import betweenness_world.level02 --hide
open IncidencePlane --hide

/-
# Betweenness World

## Level 3: proof, proof, proof!

To solve this level, the mathematical proof in paper will be given to you. Remember that you can use theorem statements from previous worlds.

**Claim:** A point that lies between two different collinear points shares the same line with them.

**Proof:** Let B be the point that lies between A and B, where these two are different collinear points that lie on the line `r`.

**(i)** Let us assume that there exists a line ℓ such that A ∈ ℓ ∧ B ∈ ℓ ∧ C ∈ ℓ. By the first axiom of order `collinear_of_between`, since A * B * C,
we prove that there exists a line ℓ such that A ∈ ℓ ∧ B ∈ ℓ ∧ C ∈ ℓ. Let this line be called `s`. Then, A ∈ s ∧ B ∈ s ∧ C ∈ s.

**(ii)** Let us assume that A ≠ C. By contradiction, if A = C, then A * B * C would be equal to C * B * C. By the lemma `no_point_between_a_point`, this is
not possible, so we prove that A ≠ C.

**(iii)** Let us assume that r = s. By the lemma `equal_lines_of_contain_two_points`, since A ≠ C, A ∈ r, A ∈ s, C ∈ r and C ∈ s, then we prove that
r = s. Because r = s, then B ∈ s, which we proved in **(i)**, must be equivalent to B ∈ r. Therefore, the point B shares the same line `r` with the points
A and C and satisfies that A * B * C.

Hence, we have shown that a point that lies between two different collinear points shares the same line with them.

-/

/- Hint : Click here for a hint, in case you get stuck.
Whenever we have a hypothesis of the form `h : P ∧ Q ∧ R`, we write `h.1` to refer to `P` and we type `h.2` to refer to `Q ∧ R`. If we want to refer to just
`Q`, we need to write `h.2.1`. Analogously, if we want to refer to just `R`, then we type `h.2.2`.

In case you don't know how to use the lemma `no_point_between_a_point`, look how you proved it in the previous level, so that you can adapt that code
for this one.

Still bewildered? Click on "View source" (located on the top right corner of the game screen) to see the solution.
-/

variables {Ω : Type} [IncidencePlane Ω] --hide
variables {A B C P Q R : Ω} --hide
variables {ℓ r s t : Line Ω} --hide

/- Lemma :
A point that lies between two different collinear points shares the same line with them.
-/
lemma between_points_share_line (hAr : A ∈ r) (hCr : C ∈ r) :
	(A * B * C) → B ∈ r :=
begin
    intro H,
	have h : ∃ ℓ, A ∈ ℓ ∧ B ∈ ℓ ∧ C ∈ ℓ,
    apply collinear_of_between,
    exact H,
    cases h with s h1,
    have hAC : A ≠ C,
    {
      intro hAC,
      rw hAC at H,
      have hCBC : C ≠ B ∧ C ≠ C ∧ B ≠ C,
      apply different_of_between,
      exact H,
      cases hCBC with hCB hCC,
      cases hCC with hC hBC,
      apply hC,
      refl,
    },
    have hrs : r = s,
    exact equal_lines_of_contain_two_points hAC hAr h1.1 hCr h1.2.2,
    rw ← hrs at h1,
    exact h1.2.1,
end


/-
Copyright (c) 2021 Gabriel Moise. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Moise, Yaël Dillies, Kyle Miller
-/
import combinatorics.simple_graph.basic
import data.matrix.basic

/-!
# Incidence matrix of a simple graph

This file defines the unoriented incidence matrix of a simple graph.

## Main definitions

* `simple_graph.inc_matrix`: `G.inc_matrix R` is the incidence matrix of `G` over the ring `R`.

## Main results

* `simple_graph.inc_matrix_mul_transpose_diag`: The diagonal entries of the product of
  `G.inc_matrix R` and its transpose are the degrees of the vertices.
* `simple_graph.inc_matrix_mul_transpose`: Gives a complete description of the product of
  `G.inc_matrix R` and its transpose; the diagonal is the degrees of each vertex, and the
  off-diagonals are 1 or 0 depending on whether or not the vertices are adjacent.
* `simple_graph.inc_matrix_transpose_mul_diag`: The diagonal entries of the product of the
  transpose of `G.inc_matrix R` and `G.inc_matrix R` are `2` or `0` depending on whether or
  not the unordered pair is an edge of `G`.

## Implementation notes

The usual definition of an incidence matrix has one row per vertex and one column per edge.
However, this definition has columns indexed by all of `sym2 α`, where `α` is the vertex type.
This appears not to change the theory, and for simple graphs it has the nice effect that every
incidence matrix for each `simple_graph α` has the same type.

## TODO

* Define the oriented incidence matrices for oriented graphs.
* Define the graph Laplacian of a simple graph using the oriented incidence matrix from an
  arbitrary orientation of a simple graph.
-/

noncomputable theory

open finset matrix simple_graph sym2
open_locale big_operators matrix

namespace simple_graph
variables (R : Type*) {α : Type*} (G : simple_graph α)

/-- `G.inc_matrix R` is the `α × sym2 α` matrix whose `(a, e)`-entry is `1` if `e` is incident to
`a` and `0` otherwise. -/
def inc_matrix [has_zero R] [has_one R] : matrix α (sym2 α) R :=
λ a, (G.incidence_set a).indicator 1

variables {R}

lemma inc_matrix_apply [has_zero R] [has_one R] {a : α} {e : sym2 α} :
  G.inc_matrix R a e = (G.incidence_set a).indicator 1 e := rfl

/-- Entries of the incidence matrix can be computed given additional decidable instances. -/
lemma inc_matrix_apply' [has_zero R] [has_one R] [decidable_eq α] [decidable_rel G.adj]
  {a : α} {e : sym2 α} :
  G.inc_matrix R a e = if e ∈ G.incidence_set a then 1 else 0 :=
by convert rfl

section mul_zero_one_class
variables [mul_zero_one_class R] {a b : α} {e : sym2 α}

lemma inc_matrix_apply_mul_inc_matrix_apply :
  G.inc_matrix R a e * G.inc_matrix R b e = (G.incidence_set a ∩ G.incidence_set b).indicator 1 e :=
begin
  simp only [inc_matrix, set.indicator_apply, ←ite_and_mul_zero,
    pi.one_apply, mul_one, set.mem_inter_eq],
  congr,
end

lemma inc_matrix_apply_mul_inc_matrix_apply_of_not_adj (hab : a ≠ b) (h : ¬ G.adj a b) :
  G.inc_matrix R a e * G.inc_matrix R b e = 0 :=
begin
  rw [inc_matrix_apply_mul_inc_matrix_apply, set.indicator_of_not_mem],
  rw [G.incidence_set_inter_incidence_set_of_not_adj h hab],
  exact set.not_mem_empty e,
end

lemma inc_matrix_of_not_mem_incidence_set (h : e ∉ G.incidence_set a) :
  G.inc_matrix R a e = 0 :=
by rw [inc_matrix_apply, set.indicator_of_not_mem h]

lemma inc_matrix_of_mem_incidence_set (h : e ∈ G.incidence_set a) : G.inc_matrix R a e = 1 :=
by rw [inc_matrix_apply, set.indicator_of_mem h, pi.one_apply]

variables [nontrivial R]

lemma inc_matrix_apply_eq_zero_iff : G.inc_matrix R a e = 0 ↔ e ∉ G.incidence_set a :=
begin
  simp only [inc_matrix_apply, set.indicator_apply_eq_zero, pi.one_apply, one_ne_zero],
  exact iff.rfl,
end

lemma inc_matrix_apply_eq_one_iff : G.inc_matrix R a e = 1 ↔ e ∈ G.incidence_set a :=
by { convert one_ne_zero.ite_eq_left_iff, assumption }

end mul_zero_one_class

section non_assoc_semiring
variables [fintype α] [non_assoc_semiring R] {a b : α} {e : sym2 α}

lemma sum_inc_matrix_apply [decidable_eq α] [decidable_rel G.adj] :
  ∑ e, G.inc_matrix R a e = G.degree a :=
by simp [inc_matrix_apply', sum_boole, set.filter_mem_univ_eq_to_finset]

lemma inc_matrix_mul_transpose_diag [decidable_eq α] [decidable_rel G.adj] :
  (G.inc_matrix R ⬝ (G.inc_matrix R)ᵀ) a a = G.degree a :=
begin
  rw ←sum_inc_matrix_apply,
  simp [matrix.mul_apply, inc_matrix_apply', ←ite_and_mul_zero],
end

lemma sum_inc_matrix_apply_of_mem_edge_set : e ∈ G.edge_set → ∑ a, G.inc_matrix R a e = 2 :=
begin
  classical,
  refine e.ind _,
  intros a b h,
  rw mem_edge_set at h,
  rw [←nat.cast_two, ←card_doubleton h.ne],
  simp only [inc_matrix_apply', sum_boole, mk_mem_incidence_set_iff, h, true_and],
  congr' 2,
  ext e,
  simp only [mem_filter, mem_univ, true_and, mem_insert, mem_singleton],
end

lemma sum_inc_matrix_apply_of_not_mem_edge_set (h : e ∉ G.edge_set) : ∑ a, G.inc_matrix R a e = 0 :=
sum_eq_zero $ λ a _, G.inc_matrix_of_not_mem_incidence_set $ λ he, h he.1

lemma inc_matrix_transpose_mul_diag [decidable_rel G.adj] :
  ((G.inc_matrix R)ᵀ ⬝ G.inc_matrix R) e e = if e ∈ G.edge_set then 2 else 0 :=
begin
  classical,
  simp only [matrix.mul_apply, inc_matrix_apply', transpose_apply, ←ite_and_mul_zero,
    one_mul, sum_boole, and_self],
  split_ifs with h,
  { revert h,
    refine e.ind _,
    intros v w h,
    rw [←nat.cast_two, ←card_doubleton (G.ne_of_adj h)],
    simp [mk_mem_incidence_set_iff, G.mem_edge_set.mp h],
    congr' 2,
    ext u,
    simp, },
  { revert h,
    refine e.ind _,
    intros v w h,
    simp [mk_mem_incidence_set_iff, G.mem_edge_set.not.mp h], },
end

end non_assoc_semiring

section semiring
variables [fintype (sym2 α)] [semiring R] {a b : α} {e : sym2 α}

lemma inc_matrix_mul_transpose_apply_of_adj (h : G.adj a b) :
  (G.inc_matrix R ⬝ (G.inc_matrix R)ᵀ) a b = (1 : R) :=
begin
  simp_rw [matrix.mul_apply, matrix.transpose_apply, inc_matrix_apply_mul_inc_matrix_apply,
    set.indicator_apply, pi.one_apply, sum_boole],
  convert nat.cast_one,
  convert card_singleton ⟦(a, b)⟧,
  rw [←coe_eq_singleton, coe_filter_univ],
  exact G.incidence_set_inter_incidence_set_of_adj h,
end

lemma inc_matrix_mul_transpose [fintype α] [decidable_eq α] [decidable_rel G.adj] :
  G.inc_matrix R ⬝ (G.inc_matrix R)ᵀ = λ a b,
    if a = b then G.degree a else if G.adj a b then 1 else 0 :=
begin
  ext a b,
  split_ifs with h h',
  { subst b,
    convert G.inc_matrix_mul_transpose_diag },
  { exact G.inc_matrix_mul_transpose_apply_of_adj h' },
  { simp only [matrix.mul_apply, matrix.transpose_apply,
    G.inc_matrix_apply_mul_inc_matrix_apply_of_not_adj h h', sum_const_zero] }
end

end semiring
end simple_graph


import game.sup_inf.supProdSets

namespace xena -- hide

/-
# Chapter 3 : Sup and Inf

## Level 11
-/

def embedded_rationals : set ℝ := {x : ℝ | ∃ y : ℚ, x = ↑y}

/- Lemma
The set of rational numbers does not have a supremum
-/
lemma not_lub_rationals : ∀ b : ℝ, ¬ (is_lub (embedded_rationals) b) :=
begin
intros b Hlub,
have Hbub : b ∈ upper_bounds embedded_rationals := Hlub.left,
have H : b < (b+1) := calc b = b+0 : (add_zero _).symm
                         ... < b+1 : add_lt_add_left zero_lt_one _,
cases (exists_rat_btwn H) with q Hq,
have Hqin : ↑q ∈ embedded_rationals := ⟨q,rfl⟩,
have Hwrong2 := Hbub Hqin,
exact not_lt.2 Hwrong2 (Hq.left),
end

end xena -- hide


/-
Copyright (c) 2021 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn
-/
import algebra.order.field.basic
import algebra.order.field.canonical.defs
import algebra.order.field.inj_surj
import algebra.order.nonneg.ring

/-!
# Semifield structure on the type of nonnegative elements

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

This file defines instances and prove some properties about the nonnegative elements
`{x : α // 0 ≤ x}` of an arbitrary type `α`.

This is used to derive algebraic structures on `ℝ≥0` and `ℚ≥0` automatically.

## Main declarations

* `{x : α // 0 ≤ x}` is a `canonically_linear_ordered_semifield` if `α` is a `linear_ordered_field`.
-/

open set

variables {α : Type*}

namespace nonneg

section linear_ordered_semifield
variables [linear_ordered_semifield α] {x y : α}

instance has_inv : has_inv {x : α // 0 ≤ x} := ⟨λ x, ⟨x⁻¹, inv_nonneg.2 x.2⟩⟩

@[simp, norm_cast]
protected lemma coe_inv (a : {x : α // 0 ≤ x}) : ((a⁻¹ : {x : α // 0 ≤ x}) : α) = a⁻¹ := rfl

@[simp]

instance has_div : has_div {x : α // 0 ≤ x} := ⟨λ x y, ⟨x / y, div_nonneg x.2 y.2⟩⟩

@[simp, norm_cast] protected lemma coe_div (a b : {x : α // 0 ≤ x}) :
  ((a / b : {x : α // 0 ≤ x}) : α) = a / b := rfl

@[simp] lemma mk_div_mk (hx : 0 ≤ x) (hy : 0 ≤ y) :
  (⟨x, hx⟩ : {x : α // 0 ≤ x}) / ⟨y, hy⟩ = ⟨x / y, div_nonneg hx hy⟩ := rfl

instance has_zpow : has_pow {x : α // 0 ≤ x} ℤ := ⟨λ a n, ⟨a ^ n, zpow_nonneg a.2 _⟩⟩

@[simp, norm_cast] protected lemma coe_zpow (a : {x : α // 0 ≤ x}) (n : ℤ) :
  ((a ^ n : {x : α // 0 ≤ x}) : α) = a ^ n := rfl

@[simp] lemma mk_zpow (hx : 0 ≤ x) (n : ℤ) :
  (⟨x, hx⟩ : {x : α // 0 ≤ x}) ^ n = ⟨x ^ n, zpow_nonneg hx n⟩ := rfl

instance linear_ordered_semifield : linear_ordered_semifield {x : α // 0 ≤ x} :=
subtype.coe_injective.linear_ordered_semifield _ nonneg.coe_zero nonneg.coe_one nonneg.coe_add
    nonneg.coe_mul nonneg.coe_inv nonneg.coe_div (λ _ _, rfl) nonneg.coe_pow nonneg.coe_zpow
    nonneg.coe_nat_cast (λ _ _, rfl) (λ _ _, rfl)

end linear_ordered_semifield

instance canonically_linear_ordered_semifield [linear_ordered_field α] :
  canonically_linear_ordered_semifield {x : α // 0 ≤ x} :=
{ ..nonneg.linear_ordered_semifield, ..nonneg.canonically_ordered_comm_semiring }

instance linear_ordered_comm_group_with_zero [linear_ordered_field α] :
  linear_ordered_comm_group_with_zero {x : α // 0 ≤ x} :=
infer_instance

end nonneg


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import data.finset.card
import data.multiset.nat_antidiagonal

/-!
# Antidiagonals in ℕ × ℕ as finsets

This file defines the antidiagonals of ℕ × ℕ as finsets: the `n`-th antidiagonal is the finset of
pairs `(i, j)` such that `i + j = n`. This is useful for polynomial multiplication and more
generally for sums going from `0` to `n`.

## Notes

This refines files `data.list.nat_antidiagonal` and `data.multiset.nat_antidiagonal`.
-/

namespace finset
namespace nat

/-- The antidiagonal of a natural number `n` is
    the finset of pairs `(i, j)` such that `i + j = n`. -/
def antidiagonal (n : ℕ) : finset (ℕ × ℕ) :=
⟨multiset.nat.antidiagonal n, multiset.nat.nodup_antidiagonal n⟩

/-- A pair (i, j) is contained in the antidiagonal of `n` if and only if `i + j = n`. -/
@[simp] lemma mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} :
  x ∈ antidiagonal n ↔ x.1 + x.2 = n :=
by rw [antidiagonal, mem_def, multiset.nat.mem_antidiagonal]

/-- The cardinality of the antidiagonal of `n` is `n + 1`. -/
@[simp] lemma card_antidiagonal (n : ℕ) : (antidiagonal n).card = n+1 :=
by simp [antidiagonal]

/-- The antidiagonal of `0` is the list `[(0, 0)]` -/
@[simp] lemma antidiagonal_zero : antidiagonal 0 = {(0, 0)} :=
rfl

lemma antidiagonal_succ (n : ℕ) :
  antidiagonal (n + 1) = cons (0, n + 1) ((antidiagonal n).map
  (function.embedding.prod_map ⟨nat.succ, nat.succ_injective⟩ (function.embedding.refl _)))
  (by simp) :=
begin
  apply eq_of_veq,
  rw [cons_val, map_val],
  { apply multiset.nat.antidiagonal_succ },
end

lemma antidiagonal_succ' (n : ℕ) :
  antidiagonal (n + 1) = cons (n + 1, 0) ((antidiagonal n).map
  (function.embedding.prod_map (function.embedding.refl _) ⟨nat.succ, nat.succ_injective⟩))
  (by simp) :=
begin
  apply eq_of_veq,
  rw [cons_val, map_val],
  exact multiset.nat.antidiagonal_succ',
end

lemma antidiagonal_succ_succ' {n : ℕ} :
  antidiagonal (n + 2) =
    cons (0, n + 2)
      (cons (n + 2, 0) ((antidiagonal n).map
        (function.embedding.prod_map ⟨nat.succ, nat.succ_injective⟩ ⟨nat.succ, nat.succ_injective⟩))
        $ by simp) (by simp) :=
by { simp_rw [antidiagonal_succ (n + 1), antidiagonal_succ', finset.map_cons, map_map], refl }

lemma map_swap_antidiagonal {n : ℕ} :
  (antidiagonal n).map ⟨prod.swap, prod.swap_right_inverse.injective⟩ = antidiagonal n :=
eq_of_veq $ by simp [antidiagonal, multiset.nat.map_swap_antidiagonal]

/-- A point in the antidiagonal is determined by its first co-ordinate. -/
lemma antidiagonal_congr {n : ℕ} {p q : ℕ × ℕ} (hp : p ∈ antidiagonal n)
  (hq : q ∈ antidiagonal n) : p = q ↔ p.fst = q.fst :=
begin
  refine ⟨congr_arg prod.fst, (λ h, prod.ext h ((add_right_inj q.fst).mp _))⟩,
  rw mem_antidiagonal at hp hq,
  rw [hq, ← h, hp],
end

lemma antidiagonal.fst_le {n : ℕ} {kl : ℕ × ℕ} (hlk : kl ∈ antidiagonal n) :
  kl.1 ≤ n :=
begin
  rw le_iff_exists_add,
  use kl.2,
  rwa [mem_antidiagonal, eq_comm] at hlk
end

lemma antidiagonal.snd_le {n : ℕ} {kl : ℕ × ℕ} (hlk : kl ∈ antidiagonal n) :
  kl.2 ≤ n :=
begin
  rw le_iff_exists_add,
  use kl.1,
  rwa [mem_antidiagonal, eq_comm, add_comm] at hlk
end

lemma filter_fst_eq_antidiagonal (n m : ℕ) :
  filter (λ x : ℕ × ℕ, x.fst = m) (antidiagonal n) = if m ≤ n then {(m, n - m)} else ∅ :=
begin
  ext ⟨x, y⟩,
  simp only [mem_filter, nat.mem_antidiagonal],
  split_ifs with h h,
  { simp [and_comm, eq_tsub_iff_add_eq_of_le h, add_comm] {contextual := tt} },
  { rw not_le at h,
    simp only [not_mem_empty, iff_false, not_and],
    exact λ hn, ne_of_lt (lt_of_le_of_lt (le_self_add.trans hn.le) h) }
end

lemma filter_snd_eq_antidiagonal (n m : ℕ) :
  filter (λ x : ℕ × ℕ, x.snd = m) (antidiagonal n) = if m ≤ n then {(n - m, m)} else ∅ :=
begin
  have : (λ (x : ℕ × ℕ), x.snd = m) ∘ prod.swap = (λ (x : ℕ × ℕ), x.fst = m),
  { ext, simp },
  rw ←map_swap_antidiagonal,
  simp [map_filter, this, filter_fst_eq_antidiagonal, apply_ite (finset.map _)]
end

section equiv_prod

/-- The disjoint union of antidiagonals `Σ (n : ℕ), antidiagonal n` is equivalent to the product
    `ℕ × ℕ`. This is such an equivalence, obtained by mapping `(n, (k, l))` to `(k, l)`. -/
@[simps] def sigma_antidiagonal_equiv_prod : (Σ (n : ℕ), antidiagonal n) ≃ ℕ × ℕ :=
{ to_fun := λ x, x.2,
  inv_fun := λ x, ⟨x.1 + x.2, x, mem_antidiagonal.mpr rfl⟩,
  left_inv :=
    begin
      rintros ⟨n, ⟨k, l⟩, h⟩,
      rw mem_antidiagonal at h,
      exact sigma.subtype_ext h rfl,
    end,
  right_inv := λ x, rfl }

end equiv_prod

end nat

end finset


import init.data.nat.basic
import init.data.int.basic

section
-- * Quant  itfiers + Equality
-- *  Universal

--
-- object of type α → Prop. In that case, given x : α, p x denotes the assertion
-- that p holds of x. Similarly, an object r : α → α → Prop denotes a binary
-- relation on α: given x y : α, r x y denotes the assertion that x is related
-- to y.

-- The universal quantifier, ∀ x : α, p x is supposed to denote the assertion
-- that “for every x : α, p x” holds. As with the propositional connectives, in
-- systems of natural deduction, “forall” is governed by an introduction and
-- elimination rule. Informally, the introduction rule states:

-- Given a proof of p x, in a context where x : α is arbitrary, we obtain a proof ∀ x : α, p x.

-- The elimination rule states:

--   Given a proof ∀ x : α, p x and any term t : α, we obtain a proof of p t.

variables (α : Type*) (p q : α → Prop)

example : (∀ x : α, p x ∧ q x) → ∀ y : α, p y  :=
assume h : ∀ x : α, p x ∧ q x,
assume y : α,
show p y, from (h y).left

-- INTRODUCTION AND ELIMINATION

--The canonical way to prove ∀ y : α, p y is to take an arbitrary y, and prove p
-- y. This is the introduction rule. Now, given that h has type ∀ x : α, p x ∧ q
-- x, the expression h y has type p y ∧ q y. This is the elimination rule.

-- TWO WAYS OF PROVING TRANSITIVITY
variables (r : α → α → Prop)
variable  trans_r : ∀ x y z, r x y → r y z → r x z

variables a b c : α
variables (hab : r a b) (hbc : r b c)

#check trans_r    -- ∀ (x y z : α), r x y → r y z → r x z
#check trans_r a b c
#check trans_r a b c hab
#check trans_r a b c hab hbc

-- NOTe THE IMPLICIT ∀ {x y z} !!!!!!!
variable  trans_r2 : ∀ {x y z}, r x y → r y z → r x z
variables (a2 b2 c2 : α)
variables (hab2 : r a2 b2) (hbc2 : r b2 c2)

#check trans_r2
#check trans_r2 hab2
#check trans_r2 hab2 hbc2

variable refl_r : ∀ x, r x x
variable symm_r : ∀ {x y}, r x y → r y x

example (a b c d : α) (hab : r a b) (hcb : r c b) (hcd : r c d) :
  r a d :=
trans_r2 (trans_r2 hab (symm_r hcb)) hcd

-- BIG BRAIN THING THAT I DONT UNDERSTAND

--  The impredicativity of Prop means that we can form propositions that
--  quantify over α → Prop. In particular, we can define predicates on α by
--  quantifying over all predicates on α, which is exactly the type of
--  circularity that was once considered problematic.

-- * Equality

-- ** eq.refl _
variables (β : Type*)

example (f : α → β) (a : α) : (λ x, f x) a = f a := eq.refl _
example (a : α) (b : α) : (a, b).1 = a := eq.refl _
example : 2 + 3 = 5 := eq.refl _

-- **  refl
example (f : α → β) (a : α) : (λ x, f x) a = f a := rfl
example (a : α) (b : α) : (a, b).1 = a := rfl
example : 2 + 3 = 5 := rfl
-- **   eq.subst h1 h2. or h1 ▸ h2
example (α : Type*) (a b : α) (p : α → Prop)
  (h1 : a = b) (h2 : p a) : p b :=
eq.subst h1 h2

example (α : Type*) (a b : α) (p : α → Prop)
  (h1 : a = b) (h2 : p a) : p b := h1 ▸ h2

-- ** congr_arg, conrg_fun, congr

-- Specifically, congr_arg can be used to replace the argument, congr_fun can be
-- used to replace the term that is being applied, and congr can be used to
-- replace both at once.

variables a3 b3 : α
variables f g : α → ℕ
variable h₁ : a3 = b3
variable h₂ : f = g

example : f a3 = f b3 := congr_arg f h₁
example : f a3 = g a3 := congr_fun h₂ a3
example : f a3 = g b3 := congr h₂ h₁
-- ** useful identities in standard library

-- import data.int.basic
-- variables a4 b4 c4 d : ℤ

-- example : a4 + 0 = a4 := add_zero a4
-- example : 0 + a4 = a4 := zero_add a4
-- example : a4 * 1 = a4 := mul_one a4
-- example : 1 * a4 = a4 := one_mul a4
-- example : -a4 + a4 = 0 := neg_add_self a4
-- example : a4 + -a4 = 0 := add_neg_self a4
-- example : a4 - a4 = 0 := sub_self a4
-- example : a4 + b4 = b4 + a4 := add_comm a4 b4
-- example : a4 + b4 + c4 = a4 + (b4 + c4) := add_assoc a4 b4 c4
-- example : a4 * b4 = b4 * a4 := mul_comm a4 b4
-- example : a4 * b4 * c4 = a4 * (b4 * c4) := mul_assoc a4 b4 c4
-- example : a4 * (b4 + c4) = a * b4 + a4 * c4 := mul_add a4 b4 c4
-- example : a4 * (b4 + c4) = a4 * b4 + a4 * c4 := left_distrib a4 b4 c4
-- example : (a4 + b4) * c4 = a4 * c4 + b4 * c4 := add_mul a4 b4 c4
-- example : (a4 + b4) * c4 = a4 * c4 + b4 * c4 := right_distrib a4 b4 c4
-- example : a4 * (b4 - c4) = a4 * b4 - a4 * c4 := mul_sub a4 b4 c4
-- example : (a4 - b4) * c4 = a4 * c4 - b4 * c := sub_mul a4

-- no idea why the identifiers are not found
variables x y z : ℤ

example (x y z : ℕ) : x * (y + z) = x * y + x * z :=  mul_add x y z
example (x y z : ℕ) : (x + y) * z = x * z + y * z := add_mul x y z
example (x y z : ℕ) : x + y + z = x + (y + z) := nat.add_assoc x y z

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
have h1 : (x + y) * (x + y) = (x + y) * x + (x + y) * y,
  from mul_add (x + y) x y,
have h2 : (x + y) * (x + y) = x * x + y * x + (x * y + y * y),
  from (add_mul x y x) ▸ (add_mul x y y) ▸ h1,
h2.trans (add_assoc (x * x + y * x) (x * y) (y * y)).symm

end

-- *  Calculational proofs
section

variables (a b c d e : ℕ)

variable h1 : a = b
variable h2 : b = c + 1
variable h3 : c = d
variable h4 : e = 1 + d

theorem T : a = e :=
calc
  a     = b      : h1
    ... = c + 1  : h2
    ... = d + 1  : congr_arg _ h3
    ... = 1 + d  : nat.add_comm d (1 : ℕ)
    ... =  e     : eq.symm h4

-- Beautiful equational proofs;

-- it is not always clear what the names in rw h1 refer to (though, in this
-- case, it is). For that reason, section variables and variables that only
-- appear in a tactic command or block are not automatically added to the
-- context. The include command takes care of that.

include h1 h2 h3 h4

theorem T2 : a = e :=
calc
  a     = b      : by rw h1
    ... = c + 1  : by rw h2
    ... = d + 1  : by rw h3
    ... = 1 + d  : by rw nat.add_comm
    ... =  e     : by rw h4

-- Equivalently

theorem T3 : a = e :=
calc
  a     = d + 1  : by rw [h1, h2, h3]
    ... = 1 + d  : by rw nat.add_comm
    ... = e      : by rw h4

-- simp tactic The simp tactic, instead, rewrites the goal by applying the given
-- identities repeatedly, in any order, anywhere they are applicable in a term.
-- It also uses other rules that have been previously declared to the system,
-- and applies commutativity wisely to avoid looping. As a result, we can also
-- prove the theorem as follows:

theorem T4 : a = e :=
by simp [h1, h2, h3, h4, nat.add_comm]

end

-- The calc command can be configured for any relation that supports some form
-- of transitivity. It can even combine different relations.

section

theorem T5 (a b c d : ℕ)
  (h1 : a = b) (h2 : b ≤ c) (h3 : c + 1 < d) : a < d :=
calc
  a     = b     : h1
    ... < b + 1 : nat.lt_succ_self b
    ... ≤ c + 1 : nat.succ_le_succ h2
    ... < d     : h3

end

-- ** Proving stuff from previous section using calculational style
-- obviouly mul_add is not found messing up with the proof lol

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
calc
  (x + y) * (x + y) = (x + y) * x + (x + y) * y  : by rw mul_add
    ... = x * x + y * x + (x + y) * y            : by rw add_mul
    ... = x * x + y * x + (x * y + y * y)        : by rw add_mul
    ... = x * x + y * x + x * y + y * y          : by rw ←add_assoc

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
by rw [mul_add, add_mul, add_mul, ←add_assoc]

example (x y : ℕ) :
  (x + y) * (x + y) = x * x + y * x + x * y + y * y :=
by simp [mul_add, add_mul, add_assoc, add_left_comm]

-- * Existential
-- ** Introduction : exists intro

-- The introduction rule is straightforward: to prove ∃ x : α, p x, it suffices
-- to provide a suitable term t and a proof of p t. here are some examples:

open nat

example : ∃ x : ℕ, x > 0 :=
have h : 1 > 0, from zero_lt_succ 0,
exists.intro 1 h

example (x : ℕ) (h : x > 0) : ∃ y, y < x :=
exists.intro 0 h

example (x y z : ℕ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z :=
exists.intro y (and.intro hxy hyz)

#check @exists.intro

-- *** ⟨ ⟩ as anonymous constructor for exists
-- We can use the anonymous constructor notation ⟨t, h⟩ for exists.intro t h,
-- when the type is clear from the context.

example : ∃ x : ℕ, x > 0 :=
⟨1, zero_lt_succ 0⟩

example (x : ℕ) (h : x > 0) : ∃ y, y < x :=
⟨0, h⟩

example (x y z : ℕ) (hxy : x < y) (hyz : y < z) :
  ∃ w, x < w ∧ w < z := ⟨y, hxy, hyz⟩
-- ** Elimination : exists.elim

-- The existential elimination rule, exists.elim allows us to prove a
-- proposition q from ∃ x : α, p x, by showing that q follows from p w for an
-- arbitrary value w. Roughly speaking, since we know there is an x satisfying p
-- x, we can give it a name, say, w. If q does not mention w, then showing that
-- q follows from p w is tantamount to showing the q follows from the existence
-- of any such x.

variables (α : Type*) (p q : α → Prop)

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
exists.elim h
  (assume w,
    assume hw : p w ∧ q w,
    show ∃ x, q x ∧ p x, from ⟨w, hw.right, hw.left⟩)

-- ↑↑↑ in the above example we first elim the first ∃
-- by naming it w, then we prove the goal of form ∃
-- by exists.introing it !
-- So we actually had to assume the var in which the ∃ works
-- together the proposition of interest.
-- Then we used to instantiated variables to construct
-- the new goal, which in this examples also happens to be of
-- the form ∃.

-- *** existential elimination with match

example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
match h with ⟨w, hw⟩ :=
  ⟨w, hw.right, hw.left⟩
end

-- alternatively,
example (h : ∃ x, p x ∧ q x) : ∃ x, q x ∧ p x :=
match h with ⟨w, hpw, hqw⟩ :=
  ⟨w, hqw, hpw⟩
end
-- or even,

example : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x :=
assume ⟨w, hpw, hqw⟩, ⟨w, hqw, hpw⟩

-- *** constructivism and or

-- Just as the constructive “or” is stronger than the classical “or,” so, too,
-- is the constructive “exists” stronger than the classical “exists”. For
-- example, the following implication requires classical reasoning because, from
-- a constructive standpoint, knowing that it is not the case that every x
-- satisfies ¬ p is not the same as having a particular x that satisfies p.

open classical

-- Too tricky, gotta study this
example (h : ¬ ∀ x, ¬ p x) : ∃ x, p x :=
by_contradiction
  (assume h1 : ¬ ∃ x, p x,
    have h2 : ∀ x, ¬ p x, from
      assume x,
      assume h3 : p x,
      have h4 : ∃ x, p x, from  ⟨x, h3⟩,
      show false, from h1 h4,
    show false, from h h2)

variable r : Prop

example : (∃ x : α, r) → r := sorry
example (a : α) : r → (∃ x : α, r) := sorry
example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r := sorry
example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) := sorry

example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) := sorry
example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) := sorry
example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) := sorry
example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) := sorry

example : (∀ x, p x → r) ↔ (∃ x, p x) → r := sorry
example (a : α) : (∃ x, p x → r) ↔ (∀ x, p x) → r := sorry
example (a : α) : (∃ x, r → p x) ↔ (r → ∃ x, p x) := sorry
-- * More on the proof language
-- ** The anonymous have + this

-- To start with, we can use anonymous “have” expressions to introduce an
-- auxiliary goal without having to label it. We can refer to the last
-- expression introduced in this way using the keyword this:

variable f : ℕ → ℕ
variable h : ∀ x : ℕ, f x ≤ f (x + 1)

example : f 0 ≤ f 3 :=
have f 0 ≤ f 1, from h 0,
have f 0 ≤ f 2, from le_trans this (h 1),
show f 0 ≤ f 3, from le_trans this (h 2)

-- ** assumption tactic

-- When the goal can be inferred, we can also ask Lean instead to fill in the
-- proof by writing by assumption:This tells Lean to use the assumption tactic,
-- which, in turn, proves the goal by finding a suitable hypothesis in the local
-- context.

example : f 0 ≤ f 3 :=
have f 0 ≤ f 1, from h 0,
have f 0 ≤ f 2, from le_trans (by assumption) (h 1),
show f 0 ≤ f 3, from le_trans (by assumption) (h 2)

-- We can also ask Lean to fill in the proof by writing ‹p›, where p is the proposition whose proof we want Lean to find in the context.You can type these corner quotes using \f< and \f>, respectively. This approach is more robust than using by assumption, because the type of the assumption that needs to be inferred is given explicitly. It also makes proofs more readable.

example : f 0 ≥ f 1 → f 1 ≥ f 2 → f 0 = f 2 :=
assume : f 0 ≥ f 1,
assume : f 1 ≥ f 2,
have f 0 ≥ f 2, from le_trans this ‹f 0 ≥ f 1›,
have f 0 ≤ f 2, from le_trans (h 0) (h 1),
show f 0 = f 2, from le_antisymm this ‹f 0 ≥ f 2›

example : f 0 ≤ f 3 :=
have f 0 ≤ f 1, from h 0,
have f 1 ≤ f 2, from h 1,
have f 2 ≤ f 3, from h 2,
show f 0 ≤ f 3, from le_trans ‹f 0 ≤ f 1›
  (le_trans ‹f 1 ≤ f 2› ‹f 2 ≤ f 3›)
-- ** anonynomous assume

-- We can also assume a hypothesis without giving it a label:In contrast to the
-- usage with have, an anonymous assume needs an extra colon. The reason is that
-- Lean allows us to write assume h to introduce a hypothesis without specifying
-- it, and without the colon it would be ambiguous as to whether the h here is
-- meant as the label or the assumption. As with the anonymous have, when you
-- use an anonymous assume to introduce an assumption, that assumption can also
-- be invoked later in the proof by enclosing it in French quotes.

example : f 0 ≥ f 1 → f 0 = f 1 :=
assume : f 0 ≥ f 1,
show f 0 = f 1, from le_antisymm (h 0) this

example : f 0 ≥ f 1 → f 1 ≥ f 2 → f 0 = f 2 :=
assume : f 0 ≥ f 1,
assume : f 1 ≥ f 2,
have f 0 ≥ f 2, from le_trans ‹f 2 ≤ f 1› ‹f 1 ≤ f 0›,
have f 0 ≤ f 2, from le_trans (h 0) (h 1),
show f 0 = f 2, from le_antisymm this ‹f 0 ≥ f 2›


theorem Ex014(a b: Prop): a ∧ b ↔ b ∧ a :=
show  a ∧ b ↔ b ∧ a , from iff.intro
  (
    assume H1:a ∧ b,
    have A:a,from and.elim_left H1,
    have B:b,from and.elim_right H1,
    show b ∧ a, from and.intro B A
  )
  (
    assume H1 : b ∧ a,
    have A:b,from and.elim_left H1,
    have B:a,from and.elim_right H1,
    show a ∧ b, from and.intro B A
  )


import .reals
open classical

structure complex :=
(Re : real) (Im : real)

def pure_real (z : complex) := z.Im = 0
def pure_imaginary (z : complex) := z.Re = 0

notation `ℂ` := complex
open complex

@[reducible] noncomputable
def complex.zero : complex := ⟨0,0⟩

@[reducible] noncomputable
def complex.one : complex := ⟨1,0⟩

@[reducible] noncomputable
def complex.add : complex → complex → complex
| ⟨a, b⟩ ⟨c, d⟩ := ⟨a + c, b + d⟩

@[reducible] noncomputable
def complex.neg : complex → complex
| ⟨a, b⟩ := ⟨-a, -b⟩

@[reducible] noncomputable
def complex.mul : complex → complex → complex
| ⟨a, b⟩ ⟨c, d⟩ := ⟨a*c - b*d, a * d + b * c⟩

@[reducible] noncomputable
def complex.scalar_mul (k : real) : complex → complex
| ⟨x, y⟩ := ⟨k*x, k*y⟩

@[reducible] noncomputable
def complex.conj : complex → complex
| ⟨x, y⟩ := ⟨x, -y⟩

@[reducible] noncomputable
def complex.norm_squared : complex → real
| ⟨x, y⟩ := x*x + y*y

lemma complex.norm_squared_nonneg : ∀ z : ℂ, z.norm_squared ≥ 0
| ⟨x, y⟩ := add_nonneg (mul_self_nonneg x) (mul_self_nonneg y)

@[reducible] noncomputable
def complex.norm (z : complex) := √(complex.norm_squared z)

lemma complex.norm_nonneg : ∀ z : ℂ, z.norm ≥ 0 :=
λ z, sqrt_nonneg z.norm_squared

lemma squared_norm_eq_norm_squared : ∀ z : ℂ, z.norm * z.norm = z.norm_squared :=
λ z, sqrt_sq z.norm_squared_nonneg

noncomputable
def complex.inv (z : complex) : complex :=
  complex.scalar_mul (1/(complex.norm_squared z)) (complex.conj z)

@[reducible] noncomputable
def i : ℂ := ⟨0, 1⟩

noncomputable instance : has_coe ℝ ℂ := ⟨λ r, ⟨r, 0⟩⟩

lemma coe_pure_real : ∀ x : ℝ, pure_real x := λ x, rfl

meta def simple_complex_eq := do
  names ← tactic.intros,
  monad.mapm' tactic.cases names,
  `[simp [1, 0, (+), (*)]],
  `[simp [complex.zero, complex.one, complex.add, complex.mul]]

lemma complex.mul_comm : ∀ z w, complex.mul z w = complex.mul w z :=
  by { intros z w, cases z, cases w, simp [complex.mul],
       constructor,
       { rw mul_comm, apply congr_arg, rw mul_comm },
       { rw add_comm, apply congr; rw mul_comm } }

lemma norm_squared_eq_mul_conj
  : ∀ z, complex.mul z (complex.conj z) = ⟨complex.norm_squared z, 0⟩
  := by { intros, cases z, simp [complex.conj, complex.norm_squared, complex.mul],
          rw [mul_comm, add_neg_self] }

lemma norm_squared_eq_mul_conj'
  : ∀ z, complex.norm_squared z = Re (complex.mul z (complex.conj z))
  := by intros; rw norm_squared_eq_mul_conj

lemma mul_conj_is_pure_real
  : ∀ z, pure_real (complex.mul z (complex.conj z))
  := by intros; rw norm_squared_eq_mul_conj; simp [pure_real]

lemma scalar_mul_comm_mul
  : ∀ w z k, complex.scalar_mul k (complex.mul w z) = complex.mul w (complex.scalar_mul k z)
  := by { intros, cases z, cases w,
          simp [complex.scalar_mul, complex.mul],
          constructor; rw left_distrib; apply congr,
          { apply congr_arg, ac_refl },
          { rw ← neg_mul_eq_mul_neg, ac_refl },
          { apply congr_arg, ac_refl },
          { ac_refl } }

lemma conj_of_conj : ∀ z, complex.conj (complex.conj z) = z :=
  by intros; cases z; simp [complex.conj]

lemma norm_squared_eq_norm_squared_of_conj : ∀ z, complex.norm_squared z = complex.norm_squared (complex.conj z) := by {
  intros,
  suffices : complex.mk (complex.norm_squared z) 0 = ⟨complex.norm_squared (complex.conj z), 0⟩,
  { have := congr_arg Re this,
    simp at this, assumption },
  rw [← norm_squared_eq_mul_conj, ← norm_squared_eq_mul_conj, conj_of_conj],
  rw complex.mul_comm }

lemma norm_squared_zero_implies_zero
  : ∀ z, complex.norm_squared z = 0 → z = complex.zero
  := by {
    intros z h,
    cases z,
    dunfold complex.norm_squared at h,
    suffices : z_Re*z_Re = 0 ∧ z_Im*z_Im = 0,
    { unfold complex.zero,
      cases this,
      suffices : z_Re = 0 ∧ z_Im = 0,
      { rw [this.left, this.right] },
      by_contra, rw decidable.not_and_iff_or_not at a,
      cases a;
      apply division_ring.mul_ne_zero a a; assumption },
    by_contradiction,
    rw decidable.not_and_iff_or_not at a,
    have hnonneg1 : 0 ≤ z_Re * z_Re, { apply mul_self_nonneg },
    have hnonneg2 : 0 ≤ z_Im * z_Im, { apply mul_self_nonneg },
    have : 0 < z_Re * z_Re ∨ 0 < z_Im * z_Im,
    { cases a,
      { apply or.inl,
        apply lt_of_le_of_ne,
        exact hnonneg1,
        intro, apply a, symmetry, assumption },
      { apply or.inr,
        apply lt_of_le_of_ne,
        exact hnonneg2,
        intro, apply a, symmetry, assumption } },
    suffices : 0 < z_Re * z_Re + z_Im * z_Im,
    { have := or.inl this,
      rw ← ne_iff_lt_or_gt at this,
      apply this, symmetry, assumption },
    cases this,
    { rw add_comm,
      apply add_pos_of_nonneg_of_pos; assumption },
    { apply add_pos_of_nonneg_of_pos; assumption } }

lemma complex.mul_inv_cancel
  : ∀ z, z ≠ complex.zero → complex.mul z (complex.inv z) = complex.one
  := by {
    intros z h,
    unfold complex.inv,
    rw ← scalar_mul_comm_mul,
    rw norm_squared_eq_mul_conj,
    unfold complex.scalar_mul,
    rw mul_zero,
    have : complex.norm_squared z ≠ 0,
    { by_contra, apply h, apply norm_squared_zero_implies_zero,
      by_contra, exact a a_1 },
    rw one_div_mul_cancel this }

lemma complex.inv_mul_cancel
  : ∀ z, z ≠ complex.zero → complex.mul (complex.inv z) z = complex.one
  := by intros z h; rw complex.mul_comm; apply complex.mul_inv_cancel z h

lemma complex.left_distrib
: ∀ v w z, complex.mul v (complex.add w z)
           = complex.add (complex.mul v w) (complex.mul v z)
  := by { intros, cases v, cases w, cases z,
          simp [complex.mul, complex.add],
          constructor,
          { rw left_distrib, simp,
            apply congr_arg, apply congr_arg,
            rw left_distrib, rw neg_add },
          { rw left_distrib, simp,
            apply congr_arg, apply congr_arg,
            rw left_distrib } }

noncomputable instance : discrete_field ℂ :=
  { zero := complex.zero
  , one := complex.one
  , add := complex.add
  , mul := complex.mul
  , neg := complex.neg
  , inv := complex.inv
  , add_zero := by simple_complex_eq
  , zero_add := by simple_complex_eq
  , one_mul := by simple_complex_eq
  , mul_one := by simple_complex_eq
  , add_assoc := by simple_complex_eq
  , add_comm := by simple_complex_eq
  , mul_assoc := by { simple_complex_eq, constructor,
                      { rw left_distrib a_Re, rw right_distrib _ _ c_Re,
                        rw right_distrib, rw left_distrib, rw neg_add, rw neg_add,
                        repeat { rw mul_assoc },
                        generalize h1 : -(a_Re * (b_Im * c_Im)) = x,
                        rw (_ : a_Re * -(b_Im * c_Im) = x),
                        generalize : -(a_Im * (b_Re * c_Im)) = y,
                        generalize : a_Re * (b_Re * c_Re) = z,
                        generalize h2 : -(a_Im * b_Im) * c_Re = w,
                        rw (_ : -(a_Im * (b_Im * c_Re)) = w),
                        ac_refl,
                        { rw ← h2,
                          rw neg_eq_neg_one_mul,
                          rw neg_eq_neg_one_mul (a_Im * _),
                          ac_refl },
                        { rw ← h1,
                          rw neg_eq_neg_one_mul,
                          rw neg_eq_neg_one_mul (a_Re * _),
                          ac_refl }, },
                      { rw neg_mul_eq_mul_neg, rw neg_mul_eq_neg_mul,
                        rw left_distrib a_Im, rw right_distrib _ _ c_Im,
                        rw add_comm (b_Re * c_Im) (b_Im * c_Re),
                        rw left_distrib a_Re, rw right_distrib _ _ c_Re,
                        rw ← add_assoc, rw ← add_assoc,
                        simp,
                        apply congr, { apply congr_arg, rw mul_assoc },
                        apply congr, { apply congr_arg, rw mul_assoc },
                        apply congr, { apply congr_arg, rw mul_assoc },
                        apply congr_arg, rw mul_assoc } }
  , mul_comm := complex.mul_comm
  , zero_ne_one := by { intro h, apply real_field.zero_ne_one, simp [0, 1], simp [0, 1] at h,
                        rw (_ : real_field.zero = Re complex.zero),
                        rw (_ : real_field.one = Re complex.one),
                        rw h, refl, refl }
  , add_left_neg := by simple_complex_eq
  , mul_inv_cancel := complex.mul_inv_cancel
  , inv_mul_cancel := complex.inv_mul_cancel
  , left_distrib := complex.left_distrib
  , right_distrib := by { intros, simp [(*)],
                          rw complex.mul_comm _ c,
                          rw complex.mul_comm a c,
                          rw complex.mul_comm b c,
                          apply complex.left_distrib }
  , inv_zero := by { dsimp [complex.zero, complex.inv, complex.norm_squared],
                     rw [mul_zero, add_zero, div_zero, complex.conj],
                     dsimp [complex.scalar_mul], congr, rw mul_zero, rw zero_mul }
  , has_decidable_eq := by {
      intros x y, cases x, cases y,
      rw (_ : ({complex .  Re := x_Re, Im := x_Im} = {Re := y_Re, Im := y_Im})
            = (x_Re = y_Re ∧ x_Im = y_Im)),
      apply_instance, apply propext, constructor; intro h,
      { cases h, constructor; refl },
      { cases h, cases h_left, cases h_right, refl } } }

noncomputable
instance : decidable_eq ℂ :=
  by { intros x y, cases x, cases y,
       rw (_ : ({complex .  Re := x_Re, Im := x_Im} = {Re := y_Re, Im := y_Im})
             = (x_Re = y_Re ∧ x_Im = y_Im)),
       apply_instance, apply propext, constructor; intro h,
       { cases h, constructor; refl },
       { cases h, cases h_left, cases h_right, refl } }

notation `|`z`|` := complex.norm z

lemma norm_zero_implies_zero  : ∀ z, |z| = 0 → z = 0 :=
by { intros z h, rw (_ : 0 = √0) at h, apply norm_squared_zero_implies_zero,
     apply eq_of_sqrt_eq, apply complex.norm_squared_nonneg, refl, exact h,
     apply sqrt_unique, refl, refl, rw mul_zero }

lemma norm_of_pure_real : ∀ z, pure_real z → |z| = abs (Re z) :=
begin
  intros z hz, rw abs_eq_sq_sqrt, dsimp [complex.norm],
  rw (_ : complex.norm_squared z = z.Re * z.Re),
  cases z, dsimp [complex.norm_squared], rw (_ : z_Im = 0),
  rw [mul_zero, add_zero], exact hz,
end

lemma norm_of_zero : |0| = 0 :=
  eq.symm $ sqrt_unique (complex.norm_squared_nonneg 0) (le_refl 0)
          $ by { rw mul_zero, rw (_ : (0 : ℂ) = ⟨0, 0⟩), dsimp [complex.norm_squared], simp, refl }

lemma norm_of_one : |1| = 1 :=
  eq.symm $ sqrt_unique (complex.norm_squared_nonneg 1) zero_le_one
          $ by { rw mul_one, rw (_ : (1 : ℂ) = ⟨1, 0⟩), dsimp [complex.norm_squared], simp, refl }

@[simp]
lemma complex.unfold_add : ∀ a b c d,
  complex.mk a b + complex.mk c d = complex.mk (a + c) (b + d) := by intros; refl
@[simp]
lemma complex.unfold_neg : ∀ a b,
  -complex.mk a b = complex.mk (-a) (-b) := by intros; refl
@[simp]
lemma complex.unfold_mul : ∀ a b c d,
  complex.mk a b * complex.mk c d = complex.mk (a*c - b*d) (a * d + b * c) := by intros; refl
@[simp]
lemma complex.unfold_inv : ∀ a b,
  (complex.mk a b)⁻¹ = complex.mk (a/(a*a + b*b)) (-b/(a*a + b*b)) :=
  by { intros, generalize h : complex.mk a b = z,
       transitivity complex.scalar_mul (1/(complex.norm_squared z)) (complex.conj z), refl,
       subst h, dsimp [complex.norm_squared, complex.scalar_mul],
       rw [mul_comm _ a, ← div_eq_mul_one_div], rw [mul_comm _ (-b), ← div_eq_mul_one_div] }

@[simp]
lemma complex.unfold_conj : ∀ a b,
  complex.conj (complex.mk a b) = complex.mk a (-b) := by intros; refl

lemma norm_conj : ∀ z : ℂ, |z| = |z.conj| :=
by intro z; dsimp [complex.norm]; rw norm_squared_eq_norm_squared_of_conj

lemma norm_sub : ∀ z w : ℂ, |z - w| = |w - z| :=
by { intros, dsimp [complex.norm], rw (_ : complex.norm_squared (z + -w) = complex.norm_squared (w + -z)),
     cases z, cases w, simp, dsimp [complex.norm_squared], apply congr,
     { apply congr_arg, transitivity -(z_Re + -w_Re) * -(z_Re + -w_Re),
       rw neg_eq_neg_one_mul (z_Re + -w_Re), rw ← mul_assoc, rw mul_right_comm (-1 : ℝ),
       rw [square_neg_one, one_mul], rw [neg_add, neg_neg, add_comm] },
     { transitivity -(z_Im + -w_Im) * -(z_Im + -w_Im),
       rw neg_eq_neg_one_mul (z_Im + -w_Im), rw ← mul_assoc, rw mul_right_comm (-1 : ℝ),
       rw [square_neg_one, one_mul], rw [neg_add, neg_neg, add_comm] } }

lemma norm_neg : ∀ z, |(-z)| = |z| :=
by intro; rw [← zero_sub, norm_sub, sub_zero]

lemma complex.norm_pos : ∀ z : ℂ, z ≠ 0 → z.norm > 0 :=
begin
  intros, apply lt_of_le_of_ne,
  apply complex.norm_nonneg, apply ne.symm,
  apply mt (norm_zero_implies_zero z), assumption
end

lemma norm_squared_of_scale : ∀ x z, complex.norm_squared (complex.scalar_mul x z) = (x*x) * z.norm_squared :=
by { intros, cases z, simp [complex.scalar_mul, complex.norm_squared],
     rw left_distrib, ac_refl, }

lemma Re_additive : ∀ z w, Re (z + w) = Re z + Re w :=
by intros; cases z; cases w; simp

lemma Im_additive : ∀ z w, Im (z + w) = Im z + Im w :=
by intros; cases z; cases w; simp

lemma mul_pure_real : ∀ z w : ℂ, pure_real z → z * w = complex.scalar_mul z.Re w :=
by intros; cases z; cases w; simp [pure_real] at a; rw a; simp [complex.scalar_mul]

lemma scale_pure_real : ∀ x (z : ℂ), pure_real z → pure_real (complex.scalar_mul x z)
                      ∧ Re (complex.scalar_mul x z) = x * Re z :=
by intros; cases z; simp [pure_real] at a; rw a; simp [complex.scalar_mul, pure_real]

lemma Re_of_scale : ∀ x (z : ℂ), Re (complex.scalar_mul x z) = x * Re z :=
by intros; cases z; simp

lemma Im_of_scale : ∀ x (z : ℂ), Im (complex.scalar_mul x z) = x * Im z :=
by intros; cases z; simp

lemma Re_conj : ∀ z, Re z = Re z.conj := by intro; cases z; refl

lemma Re_neg : ∀ z, Re (-z) = -Re z :=
by intros; cases z; simp

lemma Re_sub : ∀ z w, Re (z - w) = Re z - Re w :=
by intros; cases z; cases w; simp

lemma Im_neg : ∀ z, Im (-z) = -Im z :=
by intros; cases z; simp

lemma conj_add : ∀ z w, complex.conj (z + w) = complex.conj z + complex.conj w :=
by intros; cases z; cases w; simp [complex.conj]

lemma conj_mul : ∀ z w, complex.conj (z * w) = complex.conj z * complex.conj w :=
by intros; cases z; cases w; simp [complex.conj]

lemma conj_neg : ∀ z, complex.conj (-z) = - complex.conj z :=
by intros; cases z; simp

lemma conj_sub : ∀ z w, complex.conj (z - w) = complex.conj z - complex.conj w :=
by intros; cases z; cases w; simp

lemma norm_mul : ∀ z w, |z*w| = |z|*|w| :=
begin
  intros, symmetry, apply sqrt_unique,
  apply complex.norm_squared_nonneg,
  apply mul_nonneg; apply complex.norm_nonneg,
  rw [mul_left_comm, mul_assoc, ← mul_assoc],
  rw [squared_norm_eq_norm_squared, squared_norm_eq_norm_squared],
  rw [norm_squared_eq_mul_conj' (z*w)],
  transitivity ((z * w) * (complex.conj (z * w))).Re,
  { rw [conj_mul,mul_left_comm, mul_assoc, ← mul_assoc, mul_comm _ z],
    rw norm_squared_eq_mul_conj', rw norm_squared_eq_mul_conj',
    rw [mul_pure_real, Re_of_scale], congr,
    apply mul_conj_is_pure_real },
  refl
end

lemma norm_squared_eq_mul_conj''
  : ∀ (z : ℂ), complex.norm_squared z = (z * (complex.conj z)).Re
  := norm_squared_eq_mul_conj'

lemma norm_inv : ∀ z, z ≠ 0 → |z⁻¹| = |z|⁻¹ :=
begin
  intros, have : z.norm_squared > 0,
  { apply lt_of_le_of_ne, apply complex.norm_squared_nonneg,
    apply ne.symm, apply mt (norm_squared_zero_implies_zero z), assumption },
  cases z, simp [complex.inv],
  simp [complex.norm, complex.norm_squared],
  rw ← field.div_mul_eq_mul_div_comm,
  rw inv_sqrt, congr,
  rw inv_eq_one_div,
  rw div_eq_mul_one_div,
  rw div_eq_mul_one_div,
  rw div_eq_mul_one_div (-z_Im),
  rw mul_right_comm,
  rw ← mul_assoc, rw ← right_distrib,
  suffices : (z_Re * (1 / (z_Re * z_Re + z_Im * z_Im)) * z_Re + -z_Im * (1 / (z_Re * z_Re + z_Im * z_Im)) * -z_Im) = 1,
  { rw this, rw one_mul },
  rw mul_right_comm, rw mul_right_comm (-z_Im),
  rw ← right_distrib,
  rw [mul_neg_eq_neg_mul_symm, neg_mul_eq_neg_mul_symm, neg_neg],
  rw ← div_eq_mul_one_div, rw div_self,
  all_goals { dsimp [complex.norm_squared] at this, try { apply ne_of_gt }, assumption }
end

lemma norm_div : ∀ z w, w ≠ 0 → |z/w| = |z|/|w| :=
begin
  intros, rw div_eq_mul_one_div, rw norm_mul, congr,
  rw [← norm_inv, inv_eq_one_div], assumption
end

lemma abs_Re_le_norm : ∀ z, abs (Re z) ≤ |z| :=
begin
  intro z, rw abs_eq_sq_sqrt, dsimp [complex.norm],
  apply sqrt_monotone, cases z, dsimp [ complex.norm_squared],
  apply le_add_of_nonneg_right, apply mul_self_nonneg
end

lemma abs_Im_le_norm : ∀ z, abs (Im z) ≤ |z| :=
begin
  intro z, rw abs_eq_sq_sqrt, dsimp [complex.norm],
  apply sqrt_monotone, cases z, dsimp [complex.norm_squared],
  apply le_add_of_nonneg_left, apply mul_self_nonneg
end

lemma complex.eq_of_really_close : ∀ {z w : ℂ}, (∀ ε, ε > 0 → |z - w| < ε) → z = w :=
begin
  intros z w h, suffices : Re z = Re w ∧ Im z = Im w, { cases z, cases w, simp at this, rw [this.left, this.right] },
  constructor,
  { apply eq_of_really_close, intros ε hε, specialize h ε hε,
    rw [sub_eq_add_neg, ← Re_neg, ← Re_additive, ← sub_eq_add_neg],
    apply lt_of_le_of_lt, apply abs_Re_le_norm, assumption },
  { apply eq_of_really_close, intros ε hε, specialize h ε hε,
    rw [sub_eq_add_neg, ← Im_neg, ← Im_additive, ← sub_eq_add_neg],
    apply lt_of_le_of_lt, apply abs_Im_le_norm, assumption }
end

lemma norm_squared_sum : ∀ z w, complex.norm_squared (z + w) = complex.norm_squared z + 2*(Re (z * w.conj)) + complex.norm_squared w :=
begin
  intros, rw norm_squared_eq_mul_conj'',
  rw norm_squared_eq_mul_conj'', rw norm_squared_eq_mul_conj'',
  rw [conj_add, left_distrib, right_distrib, right_distrib],
  rw [add_assoc, ← add_assoc (w * _)], rw ← add_assoc,
  rw [Re_additive, Re_additive], congr, rw two_mul,
  cases z, cases w, simp, ac_refl,
end

lemma complex.triangle_inequality : ∀ (x y : ℂ), |x + y| ≤ |x| + |y| :=
begin
  intros, apply nonneg_le_nonneg_of_squares_le,
  apply add_nonneg; apply complex.norm_nonneg,
  rw [squared_norm_eq_norm_squared, norm_squared_sum],
  rw FOIL, rw [squared_norm_eq_norm_squared, squared_norm_eq_norm_squared],
  rw add_assoc _ ( |x| * |y| ), rw mul_comm ( |y| ), rw ← two_mul,
  apply add_le_add_right, apply add_le_add_left,
  apply mul_le_mul_of_nonneg_left _,
  { apply le_trans, apply zero_le_one, apply le_add_of_nonneg_right zero_le_one },
  rw [norm_conj y, ← norm_mul], transitivity, apply le_abs_self,
  apply abs_Re_le_norm
end

lemma complex.triangle_inequality' : ∀ (x y z : ℂ), |x - y| ≤ |x - z| + |z - y| :=
begin
  intros, rw (_ : x - y = (x - z) + (z - y)),
  apply complex.triangle_inequality,
  rw [← add_sub_assoc, sub_add_cancel]
end

lemma complex.lt_of_lt_triangle_lt (x y z : ℂ) (ε : ℝ) :
  |x - z| < ε/2 → |y - z| < ε/2 → |x - y| < ε :=
by { intros h h', rw norm_sub at h', apply lt_of_le_of_lt (complex.triangle_inequality' x y z),
     rw [← add_self_div_two ε, ← div_add_div_same], exact add_lt_add h h' }

lemma complex.squared_dist (z w : ℂ)
  : |z - w|*|z - w| = z.norm_squared - 2 * Re (z*w.conj) + w.norm_squared :=
begin
  intros, rw squared_norm_eq_norm_squared,
  rw [norm_squared_eq_mul_conj', norm_squared_eq_mul_conj'],
  rw [two_mul], transitivity
    (complex.mul z (complex.conj z)).Re - ((z * complex.conj w).Re + (complex.conj z * w).Re) +
      (complex.mul w (complex.conj w)).Re,
  { rw [sub_eq_add_neg (Re _), neg_add, ← Re_neg, ← Re_neg],
    rw [← Re_additive, ← Re_additive, ← Re_additive],
   apply congr, refl,
   transitivity (z + -w) * complex.conj (z + -w),
   { rw sub_eq_add_neg, refl },
     transitivity z * (complex.conj z) + (-(z * complex.conj w) + -(complex.conj z * w)) + w * (complex.conj w),
     { rw [conj_add, conj_neg, FOIL],
       rw ← add_assoc, apply congr,
       { congr, rw neg_mul_eq_mul_neg, rw mul_comm, rw neg_mul_eq_mul_neg },
       { apply neg_mul_neg, } },
     refl },
  rw [← norm_squared_eq_mul_conj', ← norm_squared_eq_mul_conj'],
  apply congr_fun, apply congr_arg, apply congr_arg, apply congr_arg,
  rw Re_conj, rw [conj_mul, conj_of_conj]
end

lemma distrib_scalar_mul_over_add_complex : ∀ r z w,
  complex.scalar_mul r (z + w) = complex.scalar_mul r z + complex.scalar_mul r w :=
by { intros, cases z, cases w, simp [complex.scalar_mul],
     constructor; apply left_distrib, }

lemma distrib_complex_over_scalar_mul_add_real : ∀ x y z,
  complex.scalar_mul (x + y) z = complex.scalar_mul x z + complex.scalar_mul y z :=
by { intros, cases z, simp [complex.scalar_mul],
     constructor; apply right_distrib }

lemma scalar_mul_comm_mul'
  : ∀ w z k, complex.scalar_mul k (w * z) = w * (complex.scalar_mul k z)
  := scalar_mul_comm_mul

lemma norm_squared_div_self
  : ∀ z, complex.scalar_mul (complex.norm_squared z) (1/z) = complex.conj z :=
begin
  intro z, by_cases complex.norm_squared z = 0,
  { rw norm_squared_zero_implies_zero _ h,
    dsimp [complex.norm_squared], rw [zero_mul, zero_add],
    generalize : 1/complex.zero = w, cases w,
    simp [complex.scalar_mul] },
  cases z, simp [complex.norm_squared, complex.scalar_mul],
  rw mul_div_cancel', rw mul_div_cancel', constructor; refl,
  exact h, exact h
end


import ring_theory.polynomial.content

noncomputable theory
open_locale polynomial classical

namespace polynomial
open unique_factorization_monoid

variables {k: Type*} [field k]

lemma degree_ne_bot {a : k[X]} (ha : a ≠ 0) : a.degree ≠ ⊥ :=
  by intro h; rw degree_eq_bot at h; exact ha h

/-- Prime factors of a polynomial `a` are monic factors of `a` without duplication. -/
def prime_factors (a: k[X]) : finset (k[X]) :=
  (normalized_factors a).to_finset

/-- Radical of a polynomial `a` is a product of prime factors of `a`. -/
def radical (a: k[X]) : k[X] :=
  (prime_factors a).prod id

lemma radical_zero : radical (0 : k[X]) = 1 :=
by rw [radical, prime_factors, normalized_factors_zero, multiset.to_finset_zero, finset.prod_empty]

lemma radical_one : radical (1 : k[X]) = 1 :=
by rw [radical, prime_factors, normalized_factors_one, multiset.to_finset_zero, finset.prod_empty]

lemma radical_associated {a b : k[X]} (h : associated a b) : radical a = radical b :=
begin
  rcases iff_iff_and_or_not_and_not.mp h.eq_zero_iff with ⟨rfl, rfl⟩ | ⟨ha, hb⟩,
  { refl },
  { simp_rw [radical, prime_factors],
    rw (associated_iff_normalized_factors_eq_normalized_factors ha hb).mp h },
end

lemma radical_is_unit {a : k[X]} (h : is_unit a) : radical a = 1 :=
(radical_associated (associated_one_iff_is_unit.mpr h)).trans radical_one

lemma radical_unit {u : k[X]ˣ} : radical (↑u: k[X]) = 1 :=
radical_is_unit u.is_unit

lemma radical_unit_mul {u : k[X]ˣ} {a : k[X]} : radical (↑u * a) = radical a :=
radical_associated (associated_unit_mul_left _ _ u.is_unit)

/-- coprime polynomials have disjoint prime factors (as multisets). -/
lemma is_coprime.disjoint_normalized_factors {a b : k[X]} (hc: is_coprime a b) :
  (normalized_factors a).disjoint (normalized_factors b):=
begin
  intros x hxa hxb,
  have x_dvd_a := dvd_of_mem_normalized_factors hxa,
  have x_dvd_b := dvd_of_mem_normalized_factors hxb,
  have xp := prime_of_normalized_factor x hxa,
  exact xp.not_unit (hc.is_unit_of_dvd' x_dvd_a x_dvd_b),
end

-- coprime polynomials have disjoint prime factors (as finsets)
lemma is_coprime.disjoint_prime_factors {a b : k[X]} (hc: is_coprime a b) :
  disjoint (prime_factors a) (prime_factors b):=
begin
  exact multiset.disjoint_to_finset.mpr hc.disjoint_normalized_factors,
end

lemma _root_.is_coprime.mul_prime_factors_disj_union {a b : k[X]}
  (ha : a ≠ 0) (hb : b ≠ 0) (hc : is_coprime a b) :
  prime_factors (a * b) =
    (prime_factors a).disj_union (prime_factors b) (hc.disjoint_prime_factors) :=
begin
  rw [finset.disj_union_eq_union],
  simp_rw prime_factors,
  rw [normalized_factors_mul ha hb, multiset.to_finset_add],
end

-- possible TODO: the proof is unnecessarily long
@[simp]
lemma radical_neg_one : (-1 : k[X]).radical = 1 :=
radical_is_unit (is_unit_one.neg)

lemma radical_mul {a b : k[X]} (hc: is_coprime a b) :
  (a * b).radical = a.radical * b.radical :=
begin
  by_cases ha: a = 0,
  { subst ha, rw is_coprime_zero_left at hc,
    simp only [zero_mul, radical_zero, one_mul, radical_is_unit hc], },
  by_cases hb: b = 0,
  { subst hb, rw is_coprime_zero_right at hc,
    simp only [mul_zero, radical_zero, mul_one, radical_is_unit hc], },
  simp_rw radical,
  rw hc.mul_prime_factors_disj_union ha hb,
  rw finset.prod_disj_union (hc.disjoint_prime_factors),
end

lemma radical_neg {a : k[X]} :
  (-a).radical = a.radical :=
neg_one_mul a ▸ radical_associated $ associated_unit_mul_left a (-1) is_unit_one.neg

lemma prime_factors_pow (a: k[X]) {n: ℕ} (hn: 1 ≤ n) :
  prime_factors (a^n) = prime_factors a :=
begin
  simp_rw prime_factors,
  simp only [normalized_factors_pow],
  rw multiset.to_finset_nsmul,
  exact ne_of_gt hn,
end

lemma radical_pow (a: k[X]) {n: nat} (hn: 0 < n) :
  (a^n).radical = a.radical :=
begin
  simp_rw [radical, prime_factors_pow a hn],
end

lemma radical_dvd_self (a : k[X]) : a.radical ∣ a :=
begin
  by_cases ha : a = 0,
  { rw ha,
    apply dvd_zero },
  { rw [radical, ← finset.prod_val, ← (normalized_factors_prod ha).dvd_iff_dvd_right],
    apply multiset.prod_dvd_prod_of_le,
    rw [prime_factors, multiset.to_finset_val],
    apply multiset.dedup_le },
end

lemma radical_ne_zero (a: k[X]) : a.radical ≠ 0 :=
begin
  rw [radical, ←finset.prod_val],
  apply multiset.prod_ne_zero,
  rw prime_factors,
  simp only [multiset.to_finset_val, multiset.mem_dedup],
  exact zero_not_mem_normalized_factors _,
end

lemma radical_prime {a : k[X]} (ha: prime a) :
  a.radical = normalize a :=
begin
  rw [radical, prime_factors],
  rw normalized_factors_irreducible ha.irreducible,
  simp only [multiset.to_finset_singleton, id.def, finset.prod_singleton],
end

lemma radical_prime_pow {a : k[X]} (ha: prime a)
  {n : ℕ} (hn : 1 ≤ n): (a^n).radical = normalize a :=
begin
  rw (a.radical_pow hn),
  exact (radical_prime ha),
end

lemma radical_degree_le {a: k[X]} (ha : a ≠ 0) :
  a.radical.degree ≤ a.degree :=
begin
  exact degree_le_of_dvd (radical_dvd_self a) ha,
end

lemma radical_nat_degree_le {a : k[X]} :
  a.radical.nat_degree ≤ a.nat_degree :=
begin
  by_cases ha : a = 0,
  { rw [ha, radical_zero, nat_degree_one, nat_degree_zero] },
  { exact nat_degree_le_of_dvd (radical_dvd_self a) ha },
end

end polynomial


import data.nat.basic
import data.nat.modeq
import data.nat.gcd
import data.zmod.basic
import tactic
import algebra.euclidean_domain
import data.int.basic
import data.equiv.ring
noncomputable theory

open nat nat.modeq zmod euclidean_domain
namespace lemmas2

/- LEMMAS FOR CHINESE REMAINDER THEOREM WITH 2 CONGRUENCE RELATIONS -/

/--
Two natural numbers are equal if and only if they are mutual divisors
-/
lemma eq_iff_dvd_dvd {n m : ℕ } : n = m ↔ m ∣ n ∧ n ∣ m :=
begin
    split,
    intro H,
    rw H,
    split;
    refl,

    intro H,
    rcases H with ⟨⟨c, hc⟩, ⟨d, hd⟩⟩,
    rw hd,
    rw hc at hd,
    induction m with x hx,
    rw zero_mul at hc,
    rw hc,
    ring,

    rw mul_assoc at hd,
    have hd' : x.succ * (c * d) = x.succ,
        linarith,
    rw mul_right_eq_self_iff at hd',
    have h : d = 1,
        rw nat.mul_eq_one_iff at hd',
        exact hd'.2,
    rw h,
    ring,
    exact succ_pos',
end

/--
Given coprime natural numbers M1 M2, find the inverse of M2 M1
assuming that both are nonzero so avoid (1,0) case which is silly.
-/
lemma nat_inv (M1 M2 : ℕ ) (M1pos : 0 < M1) (M2pos : 0 < M2) (H : coprime M1 M2) :
                         ∃ b1 : ℕ, modeq M1 (b1 * M2) 1 :=
begin
    -- first cast to Z/M1 Z and get the group inverse
    have hb1 := mul_inv_eq_gcd (M2 : zmod M1),
    have H' := coprime.symm H,
    unfold coprime at *,
    rw val_nat_cast M2 at hb1,

    have H'' : (M2 % M1).gcd M1 = M2.gcd M1,
    begin
        have qr  := div_add_mod (M2 : ℤ) (M1 : ℤ ),
        have qr' : (M1 * (M2 / M1) + M2 % M1) = M2,
            begin
                rw [← int.coe_nat_div M2 M1,
                    ← int.coe_nat_mod M2 M1,
                    ← int.coe_nat_mul _ _ ,
                    ← int.coe_nat_add _ _ ,
                      int.coe_nat_inj'] at qr,
                exact qr,
            end,
        -- want to show  M2.gcd M1 ∣ (M2 % M1).gcd M1,
        have div1 : M2.gcd M1 ∣ (M2 % M1).gcd M1,
        begin
            have f1 := gcd_dvd_left M2 M1,
            have f2 := gcd_dvd_right M2 M1,
            have f3 : M2.gcd M1 ∣ M1 * (M2 / M1),
                {cases f2 with c hc,
                use c * (M2 / M1),
                rw ← mul_assoc,
                rw ← hc,},
            have f4 : M2.gcd M1∣ M1 * (M2 / M1) + M2 % M1,
                {rw qr',
                exact f1,},
            rw nat.dvd_add_right f3 at f4,
            exact dvd_gcd f4 f2,
        end,
        -- want to show  (M2 % M1).gcd M1 ∣ M2.gcd M1,
        have div2 : (M2 % M1).gcd M1 ∣ M2.gcd M1,
        begin
            have f1 := gcd_dvd_right (M2 % M1) M1,
            have f2 := gcd_dvd_left (M2 % M1) M1,
            have f3 := dvd_mul_of_dvd_left f1 (M2 / M1),
            have f4 : (M2 % M1).gcd M1 ∣ M2,
            begin
                have k := (nat.dvd_add_right f3).2 f2,
                rw qr' at k,
                exact k,
            end,
            exact dvd_gcd f4 f1,
        end,
        have div : M2.gcd M1 ∣ (M2 % M1).gcd M1 ∧ (M2 % M1).gcd M1 ∣ M2.gcd M1,
            exact ⟨div1, div2⟩,
        rw ← eq_iff_dvd_dvd at div,
        exact div,
    end,
    -- use coprimeness and equality of gcd's to get as an actual inverse
    rw [H'',H'] at hb1,
    use (M2 : zmod M1)⁻¹.val,
    --translate this to zmod M1
    rw ← nat_coe_eq_nat_coe_iff _ _ _,
    simp at *,
    rw mul_comm,

    have fact : (((M2 : zmod M1)⁻¹.val) : zmod M1) = (M2 : zmod M1)⁻¹,
    begin
        rw @nat_cast_zmod_val _ _,
        use M1pos,
    end,
    rw fact,
    exact hb1,
end

/- LEMMAS FOR CHINESE REMAINDER THEOREM WITH K CONGRUENCE RELATIONS -/

end lemmas2


import game.sup_inf.level03
import data.real.basic

namespace xena -- hide

/-
# Chapter 3 : Sup and Inf

## Level 4
-/

/-
A generalization of the result in the previous level.
-/

-- begin hide
-- these three helper results to go in sidebar
lemma two_real_ne_zero : (2:ℝ) ≠ 0 :=
begin
    intro, linarith,
end

lemma avg_lt_max {mn mx: ℝ} (H : mn < mx) : (mn+mx) / 2 < mx :=
begin
  apply (mul_lt_mul_right (show (0:ℝ)<2, by norm_num)).1,
  rw [div_mul_cancel _ (two_real_ne_zero)],
  simp [H,mul_two],
end

lemma min_lt_avg {mn mx: ℝ} (H : mn < mx) : mn < (mn+mx) / 2 :=
begin
  apply (mul_lt_mul_right (show (0:ℝ)<2, by norm_num)).1,
  rw [div_mul_cancel _ (two_real_ne_zero)],
  simp [H,mul_two],
end
-- end hide

/- Lemma
A more general version of the previous level...
-/
lemma lub_open (y : ℝ) : is_lub {x : ℝ | x < y} y :=
begin
  split,
  intro h,
  intro j,
  exact le_of_lt j,
  intro h,
  intro j,
  refine le_of_not_gt _,
  intro k,
  let c := (h+y)/2,
  have H2 := j c,
  have H : c ∈ {x : ℝ  | x < y},
  exact avg_lt_max k,
  have G := H2 H,
  have P : h < c := min_lt_avg k,
  exact not_lt.2 G P,
end

end xena -- hide


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

import linear_algebra.free_module.finite.basic
import linear_algebra.matrix.charpoly.coeff

/-!

# Characteristic polynomial

We define the characteristic polynomial of `f : M →ₗ[R] M`, where `M` is a finite and
free `R`-module. The proof that `f.charpoly` is the characteristic polynomial of the matrix of `f`
in any basis is in `linear_algebra/charpoly/to_matrix`.

## Main definition

* `linear_map.charpoly f` : the characteristic polynomial of `f : M →ₗ[R] M`.

-/

universes u v w

variables {R : Type u} {M : Type v} [comm_ring R] [nontrivial R]
variables [add_comm_group M] [module R M] [module.free R M] [module.finite R M] (f : M →ₗ[R] M)

open_locale classical matrix polynomial

noncomputable theory

open module.free polynomial matrix

namespace linear_map

section basic

/-- The characteristic polynomial of `f : M →ₗ[R] M`. -/
def charpoly : R[X] :=
(to_matrix (choose_basis R M) (choose_basis R M) f).charpoly

end basic

section coeff

lemma charpoly_monic : f.charpoly.monic := charpoly_monic _

end coeff

section cayley_hamilton

/-- The **Cayley-Hamilton Theorem**, that the characteristic polynomial of a linear map, applied
to the linear map itself, is zero.

See `matrix.aeval_self_charpoly` for the equivalent statement about matrices. -/
lemma aeval_self_charpoly : aeval f f.charpoly = 0 :=
begin
  apply (linear_equiv.map_eq_zero_iff (alg_equiv_matrix _).to_linear_equiv).1,
  rw [alg_equiv.to_linear_equiv_apply, ← alg_equiv.coe_alg_hom,
    ← polynomial.aeval_alg_hom_apply _ _ _, charpoly_def],
  exact aeval_self_charpoly _,
end

lemma is_integral : is_integral R f := ⟨f.charpoly, ⟨charpoly_monic f, aeval_self_charpoly f⟩⟩

lemma minpoly_dvd_charpoly {K : Type u} {M : Type v} [field K] [add_comm_group M] [module K M]
  [finite_dimensional K M] (f : M →ₗ[K] M) : minpoly K f ∣ f.charpoly :=
minpoly.dvd _ _ (aeval_self_charpoly f)

/-- Any endomorphism polynomial `p` is equivalent under evaluation to `p %ₘ f.charpoly`; that is,
`p` is equivalent to a polynomial with degree less than the dimension of the module. -/
lemma aeval_eq_aeval_mod_charpoly (p : R[X]) : aeval f p = aeval f (p %ₘ f.charpoly) :=
(aeval_mod_by_monic_eq_self_of_root f.charpoly_monic f.aeval_self_charpoly).symm

/-- Any endomorphism power can be computed as the sum of endomorphism powers less than the
dimension of the module. -/
lemma pow_eq_aeval_mod_charpoly (k : ℕ) : f^k = aeval f (X^k %ₘ f.charpoly) :=
by rw [←aeval_eq_aeval_mod_charpoly, map_pow, aeval_X]

variable {f}

lemma minpoly_coeff_zero_of_injective (hf : function.injective f) : (minpoly R f).coeff 0 ≠ 0 :=
begin
  intro h,
  obtain ⟨P, hP⟩ := X_dvd_iff.2 h,
  have hdegP : P.degree < (minpoly R f).degree,
  { rw [hP, mul_comm],
    refine degree_lt_degree_mul_X (λ h, _),
    rw [h, mul_zero] at hP,
    exact minpoly.ne_zero (is_integral f) hP },
  have hPmonic : P.monic,
  { suffices : (minpoly R f).monic,
    { rwa [monic.def, hP, mul_comm, leading_coeff_mul_X, ← monic.def] at this },
    exact minpoly.monic (is_integral f) },
  have hzero : aeval f (minpoly R f) = 0 := minpoly.aeval _ _,
  simp only [hP, mul_eq_comp, ext_iff, hf, aeval_X, map_eq_zero_iff, coe_comp, alg_hom.map_mul,
    zero_apply] at hzero,
  exact not_le.2 hdegP (minpoly.min _ _ hPmonic (ext hzero)),
end

end cayley_hamilton

end linear_map


import algebra.module
import analysis.inner_product_space.basic
import data.matrix.notation
import data.matrix.dmatrix
import linear_algebra.basic
import linear_algebra.bilinear_form
import linear_algebra.quadratic_form.basic
import linear_algebra.finsupp
import tactic
import linear_algebra.matrix.nonsingular_inverse

noncomputable theory

/-
According to Wikipedia, everyone's favourite reliable source of knowledge,
linear algebra studies linear equations and linear maps, representing them
in vector spaces and through matrices.

Vector spaces are special cases of modules where scalars live any semiring,
not necessarily a field.
-/

#print module
-- class module (R : Type u) (M : Type v) [semiring R] [add_comm_monoid M]
-- extends distrib_mul_action R M := ...

/-
In other words: let `R` be a semiring and `M` have `0` and a commutative operator `+`,
then a module structure over `R` on `M` has a scalar multiplication `•` (`has_smul.smul`),
which satisfies the following identities:
-/
#check add_smul -- ∀ (r s : R) (x : M), (r + s) • x = r • x + s • x
#check smul_add -- ∀ (r : R) (x y : M), r • (x + y) = r • x + r • y
#check mul_smul -- ∀ (r s : R) (x : M), (r * s) • x = r • (s • x)
#check one_smul -- ∀ (x : M), 1 • x = x
#check zero_smul -- ∀ (x : M), 0 • x = 0
#check smul_zero -- ∀ (r : R), r • 0 = 0
/-
These equations define modules (and vector spaces).
-/

/-
The last two identities follow automatically from the previous if `M` has a negation operator,
turning it into an additive group, so the function `module.of_core` does the proofs for you:
-/
#check module.of_core

section module

/-
Typical examples of modules (and vector spaces):
-/
-- import algebra.pi_instances
variables {n : Type} [fintype n]
example : module ℕ (n → ℕ) := infer_instance -- Or as mathematicians commonly know it: `ℕ^n`.
example : module ℤ (n → ℤ) := infer_instance
example : module ℚ (n → ℚ) := infer_instance

/- If you want a specifically `k`-dimensional module, use `fin k` as the `fintype`. -/
example {k : ℕ} : module ℤ (fin k → ℤ) := infer_instance

variables {R M N : Type} [ring R] [add_comm_group M] [add_comm_group N] [module R M] [module R N]
example : module R R := infer_instance
example : module ℤ R := infer_instance
example : module R (M × N) := infer_instance
example : module R (M × N) := infer_instance

example {R' : Type} [comm_ring R'] (f : R →+* R') : module R R' := ring_hom.to_module f

/- To explicitly construct elements of `fin k → R`, use the following notation: -/
-- import data.matrix.notation
example : fin 4 → ℤ := ![1, 2, -4, 3]

end module

section linear_map

variables {R M : Type} [comm_ring R] [add_comm_group M] [module R M]

/-
Maps between modules that respect `+` and `•` are called `linear_map`,
and an `R`-linear map from `M` to `N` has notation `M →ₗ[R] N`:
-/
#print linear_map

/- They are bundled, meaning we define them by giving the map and the proofs simultaneously: -/
def twice : M →ₗ[R] M :=
{ to_fun := λ x, (2 : R) • x,
  map_add' := λ x y, smul_add 2 x y,
  map_smul' := λ s x, smul_comm 2 s x }

/- Linear maps can be applied as if they were functions: -/
#check twice (![37, 42] : fin 2 → ℚ)

/- Some basic operations on linear maps: -/
-- import linear_algebra.basic
#check linear_map.comp -- composition
#check linear_map.has_zero -- 0
#check linear_map.has_add -- (+)
#check linear_map.has_smul -- (•)

/-
A linear equivalence is an invertible linear map.
These are the correct notion of "isomorphism of modules".
-/
#print linear_equiv
/- The identity function is defined twice: once as linear map and once as linear equivalence. -/
#check linear_map.id
#check linear_equiv.refl

end linear_map

section submodule

variables {R M : Type} [comm_ring R] [add_comm_group M] [module R M]

/-
The submodules of a module `M` are subsets of `M` (i.e. elements of `set M`)
that are closed under the module operations `0`, `+` and `•`.
`subspace` is defined to be a special case of `submodule`.
-/
#print submodule
#print subspace

/-
Note that the `ideal`s of a ring `R` are defined to be exactly the `R`-submodules of `R`.
This should save us a lot of re-definition work.
-/
#print ideal

/-
You can directly define a submodule by giving its carrier subset and proving that
the carrier is closed under each operation:
-/
def zero_submodule : submodule R M :=
{ carrier := {0},
  zero_mem' := by simp,
  add_mem' := by { intros x y hx hy, simp at hx hy, simp [hx, hy] },
  smul_mem' := by { intros r x hx, simp at hx, simp [hx] } }

/- There are many library functions for defining submodules: -/
variables (S T : submodule R M)
#check (twice.range : submodule R M) -- the image of `twice` in `M`
#check (twice.ker : submodule R M) -- the kernel of `twice` in `M`
#check submodule.span ℤ {(2 : ℤ)} -- also known as 2ℤ
#check S.map twice -- also known as {twice x | x ∈ S}
#check S.comap twice -- also known as {x | twice x ∈ S}

/- For submodule inclusion, we write `≤`: -/
#check S ≤ T
#check S < T
/- The zero submodule is written `⊥` and the whole module as a submodule is written `⊤`: -/
example {x : M} : x ∈ (⊥ : submodule R M) ↔ x = 0 := submodule.mem_bot R
example {x : M} : x ∈ (⊤ : submodule R M) := submodule.mem_top
/- Intersection and sum of submodules are usually written with the lattice operators `⊓` and `⊔`: -/
#check submodule.mem_inf -- x ∈ S ⊓ T ↔ x ∈ S ∧ x ∈ T
#check submodule.mem_sup -- x ∈ S ⊔ T ↔ ∃ (y ∈ S) (z ∈ T), x = y + z

/- The embedding of a submodule in the ambient space, is called `subtype`: -/
#check submodule.subtype

/- Finally, we can take the quotient modulo a submodule.
Note the nonstandard ⧸, typed with \quot .
 -/

#check ℤ ⧸ submodule.span ℤ {(2 : ℤ)}

end submodule

section forms

variables {n R : Type} [comm_ring R] [fintype n]

/- In addition to linear maps, there are bilinear forms, quadratic forms and sesquilinear forms. -/
-- import linear_algebra.bilinear_form
-- import linear_algebra.quadratic_form
#check bilin_form

/- Defining a bilinear form works similarly to defining a linear map: -/
def dot_product : bilin_form R (n → R) :=
{ bilin := λ x y, matrix.dot_product x y,
  bilin_add_left := matrix.add_dot_product,
  bilin_smul_left := matrix.smul_dot_product,
  bilin_add_right := matrix.dot_product_add,
  bilin_smul_right := matrix.dot_product_smul }

/- Some other constructions on forms: -/
#check bilin_form.to_quadratic_form
#check quadratic_form.associated
#check quadratic_form.has_smul
#check quadratic_form.proj

end forms

section matrix

variables {m n R : Type} [fintype m] [fintype n] [comm_ring R]

/-
Matrices in mathlib are basically no more than a rectangular block of entries.
Under the hood, they are specified by a function taking a row and column,
and returning the entry at that index.
They are useful when you want to compute an invariant such as the determinant,
as these are typically noncomputable for linear maps.

A type of matrices `matrix m n α` requires that the types `m` and `n` of the indices
are `fintype`s, and there is no restriction on the type `α` of the entries.
-/
#print matrix

/-
Like vectors in `n → R`, matrices are typically indexed over `fin k`.
To define a matrix, you map the indexes to the entry:
-/
def example_matrix : matrix (fin 2) (fin 3) ℤ := λ i j, i + j
#eval example_matrix 1 2

/- Like vectors, we can use `![...]` notation to define matrices: -/
def other_example_matrix : matrix (fin 3) (fin 2) ℤ :=
![![0, 1],
  ![1, 2],
  ![2, 3]]

/- We have the 0 matrix and the sum of two matrices: -/
example (i j) : (0 : matrix m n R) i j = 0 := dmatrix.zero_apply i j
example (A B : matrix m n R) (i j) : (A + B) i j = A i j + B i j := dmatrix.add_apply A B i j

/-
Matrices have multiplication and transpose operators `matrix.mul` and `matrix.transpose`.
The following line allows `⬝` and `ᵀ` to stand for these two respectively:
-/
open_locale matrix

#check example_matrix ⬝ other_example_matrix
#check example_matrixᵀ

/- On square matrices, we have a semiring structure with `(*) = (⬝)` and `1` as the identity matrix. -/
#check matrix.semiring

/-
When working with matrices, a "vector" is always of the form `n → R`
where `n` is a `fintype`. The operations between matrices and vectors are defined.
-/
#check matrix.col -- turn a vector into a column matrix
#check matrix.row -- turn a vector into a row matrix
#check matrix.vec_mul_vec -- column vector times row vector

/-
You have to explicitly specify whether vectors are multiplied on the left or on the right:
-/
#check example_matrix.mul_vec -- (fin 3 → ℤ) → (fin 2 → ℤ), right multiplication
#check matrix.vec_mul _ example_matrix -- (fin 2 → ℤ) → (fin 3 → ℤ), left multiplication

/- You can convert a matrix to a linear map, which acts by right multiplication of vectors. -/
variables {M N : Type} [add_comm_group M] [add_comm_group N] [module R M] [module R N]
#check matrix.to_lin' -- matrix m n R → ((n → R) →ₗ[R] (m → R))

/-
Going between linear maps and matrices is an isomorphism,
as long as you have chosen a basis for each module.
-/
variables [decidable_eq m] [decidable_eq n]
variables (v : basis m R M) (w : basis n R N)
#check linear_map.to_matrix v w -- (M →ₗ[R] N) ≈ₗ[R] matrix n m R

/-
Invertible (i.e. nonsingular) matrices have an inverse operation denoted by `⁻¹`.
-/
#check matrix.inv_def

end matrix

section odds_and_ends

/- Other useful parts of the library: -/
-- import analysis.inner_product_space.basic
#print normed_space -- module with a norm
#print inner_product_space -- normed space with an inner product in ℝ or ℂ

#print finite_dimensional.finrank -- the rank (or dimension) of a space, as a natural number (infinity -> 0)
#print module.rank -- the rank (or dimension) of a module (or vector space), as a cardinal

end odds_and_ends


/-
Copyright (c) 2021 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker
-/
import analysis.normed_space.ordered
import analysis.asymptotics.asymptotics

/-!
# A collection of specific asymptotic results

This file contains specific lemmas about asymptotics which don't have their place in the general
theory developped in `analysis.asymptotics.asymptotics`.
-/

open filter asymptotics
open_locale topological_space

section linear_ordered_field

variables {𝕜 : Type*} [linear_ordered_field 𝕜]

lemma pow_div_pow_eventually_eq_at_top {p q : ℕ} :
  (λ x : 𝕜, x^p / x^q) =ᶠ[at_top] (λ x, x^((p : ℤ) -q)) :=
begin
  apply ((eventually_gt_at_top (0 : 𝕜)).mono (λ x hx, _)),
  simp [zpow_sub₀ hx.ne'],
end

lemma pow_div_pow_eventually_eq_at_bot {p q : ℕ} :
  (λ x : 𝕜, x^p / x^q) =ᶠ[at_bot] (λ x, x^((p : ℤ) -q)) :=
begin
  apply ((eventually_lt_at_bot (0 : 𝕜)).mono (λ x hx, _)),
  simp [zpow_sub₀ hx.ne'.symm],
end

lemma tendsto_zpow_at_top_at_top {n : ℤ}
  (hn : 0 < n) : tendsto (λ x : 𝕜, x^n) at_top at_top :=
begin
  lift n to ℕ using hn.le,
  simp only [zpow_coe_nat],
  exact tendsto_pow_at_top (nat.succ_le_iff.mpr $int.coe_nat_pos.mp hn)
end

lemma tendsto_pow_div_pow_at_top_at_top {p q : ℕ}
  (hpq : q < p) : tendsto (λ x : 𝕜, x^p / x^q) at_top at_top :=
begin
  rw tendsto_congr' pow_div_pow_eventually_eq_at_top,
  apply tendsto_zpow_at_top_at_top,
  linarith
end

lemma tendsto_pow_div_pow_at_top_zero [topological_space 𝕜] [order_topology 𝕜] {p q : ℕ}
  (hpq : p < q) : tendsto (λ x : 𝕜, x^p / x^q) at_top (𝓝 0) :=
begin
  rw tendsto_congr' pow_div_pow_eventually_eq_at_top,
  apply tendsto_zpow_at_top_zero,
  linarith
end

end linear_ordered_field

section normed_linear_ordered_field

variables {𝕜 : Type*} [normed_linear_ordered_field 𝕜]

lemma asymptotics.is_o_pow_pow_at_top_of_lt
  [order_topology 𝕜] {p q : ℕ} (hpq : p < q) :
  is_o (λ x : 𝕜, x^p) (λ x, x^q) at_top :=
begin
  refine (is_o_iff_tendsto' _).mpr (tendsto_pow_div_pow_at_top_zero hpq),
  exact (eventually_gt_at_top 0).mono (λ x hx hxq, (pow_ne_zero q hx.ne' hxq).elim),
end

lemma asymptotics.is_O.trans_tendsto_norm_at_top {α : Type*} {u v : α → 𝕜} {l : filter α}
  (huv : is_O u v l) (hu : tendsto (λ x, ∥u x∥) l at_top) : tendsto (λ x, ∥v x∥) l at_top :=
begin
  rcases huv.exists_pos with ⟨c, hc, hcuv⟩,
  rw is_O_with at hcuv,
  convert tendsto.at_top_div_const hc (tendsto_at_top_mono' l hcuv hu),
  ext x,
  rw mul_div_cancel_left _ hc.ne.symm,
end

end normed_linear_ordered_field


theorem le_of_succ_le_succ (a b : mynat) : succ a ≤ succ b → a ≤ b :=
begin
intro h,
repeat { rw succ_eq_add_one at h },
cases h with c hc,
rw add_assoc a 1 c at hc,
rw add_comm 1 c at hc,
rw ← add_assoc a c 1 at hc,
rw add_right_cancel_iff at hc,
rw hc,
use c,
refl,
end


import init.data.set
import set_theory.cardinal.basic

open set
open_locale cardinal

theorem mk_le_of_surjective {α β : Type} {f : α → β}
  (hf : function.surjective f) :
  #α ≥ #β :=
begin
  fsplit,
  fsplit,
  exact function.surj_inv hf,
  exact function.injective_surj_inv hf,
end

theorem mk_le_of_injective {α β : Type} {f : α → β}
  (hf : function.injective f) :
  #α ≤ #β :=
begin
  fsplit,
  fsplit,
  assumption,
  assumption,
end


import data.set
import logic.basic

open set

namespace mth1001

section set_difference

variable U : Type* -- We'll have sets on the type `U`.
variables A B C : set U

-- In addition to `mem_inter_iff` and `mem_union_eq` from the previous file, we'll
-- now use `mem_diff` for the set difference

example (x : U) : x ∈ A \ B ↔ x ∈ A ∧ x ∉ B := by rw mem_diff

-- We need the follow two lines to admit classical reasoning.
open classical
local attribute [instance] prop_decidable

-- In addition to the rules of propositional logic given in the previous file, we use
-- De Morgan's laws, as follows.

example (p q : Prop) : ¬(p ∧ q) ↔ ¬p ∨ ¬q := by rw not_and_distrib
example (p q : Prop) : ¬(p ∨ q) ↔ ¬p ∧ ¬q := by rw not_or_distrib

example : A \ (B ∩ C) = (A \ B) ∪ (A \ C) :=
begin
  ext, -- Assume `x : U`. It suffices to prove `x ∈ A \ (B ∩ C) ↔ x ∈  (A \ B) ∪ (A \ C)`.
  -- Rewrite using definitions of set difference, intersection, and union.
  rw [mem_diff, mem_union_eq, mem_inter_iff, mem_diff, mem_diff],
  rw not_and_distrib,        -- De Morgan's law
  rw and_or_distrib_left,    -- Distributivity of conjunction over disjunction
end

-- Exercise 138:
-- In addition to De Morgan's law, the propositional logic aspect of the following problem requires
-- several applications of the laws featured in the previous file. If you get irritated, the
-- `tauto` tactic will close many goals in propositional logic.
example : A \ (B ∪ C) = (A \ B) ∩ (A \ C) :=
begin
  ext,
  rw [mem_diff, mem_union_eq, mem_inter_iff, mem_diff, mem_diff],
  rw not_or_distrib,        -- De Morgan's law
  rw and_comm (x ∈ A) (x ∉ B),
  sorry
end

end set_difference

end mth1001


import tactic
import data.int.parity
import data.int.modeq
import data.nat.factorization.basic
import number_theory.legendre_symbol.quadratic_reciprocity

import Mordell.CongruencesMod4

lemma int.exists_nat_of_nonneg {z : ℤ} (hz : 0 ≤ z) : ∃ n : ℕ,
  (n : ℤ) = z :=
begin
  use z.nat_abs,
  exact int.nat_abs_of_nonneg hz,
end

lemma do_we_have (x : ℤ) (h : x^2 - 2*x + 4 ≡ 3 [ZMOD 4]) :
  ∃ (p : ℕ), p.prime ∧ (p : int) ∣ (x^2 - 2*x + 4)  ∧ p ≡ 3 [MOD 4] :=
begin
  have h1 : (x^2 - 2*x + 4) = (x-1)^2 + 3:= by ring,
  have h2 : 0 ≤ x^2 - 2*x + 4,
  { nlinarith, },
  obtain ⟨n, hn⟩  := int.exists_nat_of_nonneg h2,
  rw ← hn at h,
  have h3 : n ≡ 3 [MOD 4],
  unfold int.modeq at h,
  unfold nat.modeq,
  assumption_mod_cast,
  obtain ⟨p, hp1, hp2, hp3⟩ := three_modulo_four_prime_factor n h3,
  refine ⟨p, hp1, _, hp3⟩,
  rwa [← hn, int.coe_nat_dvd],
end

example (x y : ℤ) : y^2 ≠ x^3 + 7 :=
begin
  intro heq,
  have oddx: odd x,
    { apply int.odd_iff_not_even.2,
      intro h,
      rw even_iff_two_dvd at h,
      have h3: 0 < 3,
        {
          norm_num,
        },
      rw ← int.pow_dvd_pow_iff h3 at h,
      norm_num at h,
      rw ← int.modeq_zero_iff_dvd at h,
      have h2 := int.modeq.add_right 7 h,
      rw ← heq at h2,
      norm_num at h2,
      have h4:= int.square_ne_three_mod_four y,
      apply h4,
      clear h4,
      have h6: (4 : ℤ ) ∣ 8:= by norm_num,
      have h5:= int.modeq.modeq_of_dvd h6 h2,
      unfold int.modeq at h5 ⊢,
      rw h5,
      norm_num,
  },
  have h2 : y^2+1=(x+2)*(x^2 - 2*x + 4),
    { rw heq,
      ring,
    },
  have h6 : (x-1)^2 + 3 ≡  3 [ZMOD 4],
    { rcases oddx with ⟨n,rfl⟩,
       ring_nf,
       symmetry,
       rw int.modeq_iff_dvd,
       unfold has_dvd.dvd,
       use n^2,
       ring,
    },
  have h7 : (x^2 - 2*x + 4) = (x-1)^2 + 3:= by ring,
  rw ← h7 at h6,
  obtain ⟨p, hp, hpd, h8⟩  := do_we_have x h6,
  have h9 : ↑p∣y^2 + 1,
  { rw h2,
    exact dvd_mul_of_dvd_right hpd (x + 2), },
  haveI : fact (nat.prime p) := ⟨hp⟩,
  set yp : zmod p := y with hyp0,
  have hyp : yp^2 = -1,
  { rw ← zmod.int_coe_zmod_eq_zero_iff_dvd at h9,
    push_cast at h9,
    linear_combination h9, },
  apply zmod.mod_four_ne_three_of_sq_eq_neg_one hyp,
  rw nat.modeq at h8,
  norm_num at h8,
  assumption,
end


-- vim: ts=2 sw=0 sts=-1 et ai tw=70

import .lt
import ..logic
import ..myset.basic

namespace hidden

open mynat

-- proof of the principle of strong induction
-- conceptually quite nice: works by showing that for any N, the
-- statement will hold for all M less than or equal to that N.
theorem strong_induction
(statement: mynat → Prop)
(base_case: statement 0)
(inductive_step: ∀ n: mynat,
                  (∀ m: mynat, m ≤ n → statement m)
                    → statement (succ n)):
∀ k: mynat, statement k :=
begin
  intro k,
  have h_aux: ∀ N: mynat, (∀ M: mynat, M ≤ N → statement M), {
    intro N,
    induction N with N_n N_ih, {
      simp,
      intro M,
      assume hMl0,
      have hM0 := le_zero hMl0,
      rw hM0,
      from base_case,
    }, {
      intro M,
      assume hMlesN,
      cases hMlesN with d hd,
      cases d, {
        simp at hd,
        rw ←hd,
        from inductive_step N_n N_ih,
      }, {
        have hMleN: M ≤ N_n, {
          existsi d,
          simp at hd,
          assumption,
        },
        from N_ih M hMleN,
      },
    },
  },
  from h_aux (succ k) k (le_to_add: k ≤ k + 1),
end

-- very similar. Formulating it in terms of the strict ordering
-- makes the prerequisites seem easier, but my hot take is that
-- this one is secretly much less nice
theorem strict_strong_induction
(statement: mynat → Prop)
(inductive_step: ∀ n: mynat,
                  (∀ m: mynat, m < n → statement m)
                    → statement n):
∀ k: mynat, statement k :=
begin
  apply strong_induction, {
    apply inductive_step,
    intro m,
    assume hm0,
    exfalso,
    from lt_nzero hm0,
  }, {
    intro n,
    assume h_ih,
    apply inductive_step,
    intro m,
    assume hmsn,
    apply h_ih,
    rw le_iff_lt_succ,
    assumption,
  },
end

-- can this be done in an even flashier one-term sort of way?
-- it must be possible, right
theorem lt_well_founded : well_founded lt :=
begin
  split,
  intro a,
  apply strict_strong_induction,
  intro n,
  assume h_ih,
  split,
  assumption,
end

instance: has_well_founded mynat := ⟨lt, lt_well_founded⟩

-- induction with n base cases.
-- Note the case with n = 0 is basically a direct proof,
-- the case with n = 1 is regular induction.
-- This is currently a bit of a pain to actually use, particularly for
-- proving bases cases, hence the below special case. It would be
-- really cool to have a tactic to just split the base cases into
-- goals ^_^
theorem multi_induction
(n: mynat)
(statement: mynat → Prop)
-- statement is true for 0, ..., n - 1
(base_cases: ∀ m: mynat, m < n → statement m)
-- given the statement for m, ..., m + n - 1, the statement holds for
-- m + n
(inductive_step: ∀ m: mynat,
  (∀ d: mynat, d < n → statement (m + d)) → statement (m + n)):
∀ m: mynat, statement m :=
begin
  -- I'm not sure if this proof is the nicest way to go
  apply strong_induction, {
    -- yuckily, the base case depends on if n is 0 or not
    cases n, {
      apply inductive_step,
      intro d,
      assume hd0,
      exfalso, from lt_nzero hd0,
    }, {
      apply base_cases,
      from zero_lt_succ,
    },
  }, {
    intro m,
    by_cases (n ≤ (succ m)), {
      cases h with d hd,
      rw hd,
      assume h_sih,
      rw add_comm,
      apply inductive_step,
      -- at this point it just takes a bit of wrangling to show the
      -- obvious
      intro d',
      assume hdn,
      apply h_sih,
      rw [le_iff_lt_succ, hd, add_comm],
      from lt_add hdn,
    }, {
      assume _,
      from base_cases _ h,
    },
  },
end

-- theorem for convenience
theorem duo_induction
(statement: mynat → Prop)
(h0: statement 0)
(h1: statement 1)
(inductive_step: ∀ m: mynat,
  statement m → statement (m + 1) → statement (m + 2)):
∀ m: mynat, statement m :=
begin
  apply multi_induction 2, {
    -- grind out base cases
    intro m,
    cases m, {
      assume _, assumption,
    }, {
      cases m, {
        assume _, assumption,
      }, {
        assume hcontr,
        exfalso, from lt_nzero (lt_cancel 2 hcontr),
      },
    },
  }, {
    intro m,
    intro hd,
    apply inductive_step, {
      apply hd 0,
      from zero_lt_succ,
    }, {
      apply hd 1,
      from lt_add zero_lt_succ,
    },
  },
end

-- oddly specific convenient way to prove conjunctive statements
-- about mynat
theorem induction_conjunction
(p q: mynat → Prop)
(base_case: p 0 ∧ q 0)
(inductive_step:
  ∀ n, (p n → q n → p (succ n)) ∧
       (p n → q n → p (succ n) → q (succ n))):
∀ n, p n ∧ q n :=
begin
  intro n,
  induction n, {
    from base_case,
  }, {
    have hpsn := (inductive_step n_n).left n_ih.left n_ih.right,
    split, {
      from hpsn,
    }, {
      from (inductive_step n_n).right n_ih.left n_ih.right hpsn,
    },
  },
end

open classical
local attribute [instance] prop_decidable

-- Should help prove Bezout
theorem well_ordering
{statement : mynat → Prop} :
(∃ k : mynat, statement k) →
∃ k : mynat, statement k ∧
∀ j : mynat, (statement j) →  k ≤ j :=
begin
  assume hex,
  by_contradiction h,
  rw not_exists at h,
  -- Prove by strong_induction that it's true for all
  -- if there is no smallest for which it is false.
  have hall : ∀ k : mynat, ¬(statement k), {
    apply strong_induction (λ k, ¬statement k), {
      assume hs0,
      have h0 := h 0,
      rw not_and at h0,
      have hcontra := h0 hs0,
      suffices : ∀ (j : mynat), statement j → 0 ≤ j,
        contradiction,
      intro j,
      assume _,
      from zero_le,
    }, {
      assume n hn hsucc,
      have hnall := (not_and.mp (h (succ n))) hsucc,
      cases not_forall.mp hnall with x hnx,
      cases not_imp.mp hnx with hx hnotsucclex,
      have hxlen : x ≤ n, {
        have hxltsucc : x < succ n, from hnotsucclex,
        rw ←le_iff_lt_succ at hxltsucc, assumption,
      },
      have hcontra := hn x hxlen,
      contradiction,
    },
  },
  cases hex with k hk,
  have hnk := hall k,
  contradiction,
end

-- Intuitionist given well-ordering
theorem infinite_descent
(statement : mynat → Prop) :
(∀ k : mynat, (statement k → ∃ j : mynat, statement j ∧ j < k))
→ ∀ k : mynat, ¬(statement k) :=
begin
  assume h k hk,
  have hex : ∃ k : mynat, statement k ∧
             ∀ j : mynat, (statement j) →  k ≤ j, {
    apply well_ordering,
    existsi k,
    assumption,
  },
  cases hex with i hi,
  cases hi with hil hir,
  have hallile := h i hil,
  cases hallile with j hj,
  cases hj with hjl hjr,
  have := hir j hjl,
  contradiction,
end

theorem descend_to_zero (p : mynat → Prop)
(hdec: ∀ {k}, p (succ k) → p k)
: ∀ {m}, p m → p 0
| zero := assume h, by assumption
| (succ m) := assume h, descend_to_zero (hdec h)

/-- Lemma to shorten things a bit -/
private lemma nempty_imp_exists {α : Type} {S : myset α}
(h : ¬myset.empty S) : ∃ x, x ∈ S :=
by rwa myset.exists_iff_nempty

-- TODO: Is this the right place for this? Is this the right name?
-- Could make S implicit but this makes the goal state confusing,
-- filled with _
-- Notice we don't need to prove that there is a unique such element,
-- though we do later to actually make it useful
/-- Given a non-empty set of mynats, (noncomputably) get its least element, via
well-ordering -/
noncomputable def min (S : myset mynat) (h : ¬myset.empty S) : mynat :=
classical.some (well_ordering (nempty_imp_exists h))

-- some_spec is a proof that `some` satifies its definition
theorem min_property {S : myset mynat} (hS : ¬myset.empty S) :
min S hS ∈ S :=
(classical.some_spec (well_ordering (nempty_imp_exists hS))).left

theorem min_le {S : myset mynat} {m : mynat}
(hS : ¬myset.empty S) (hm : m ∈ S) : min S hS ≤ m :=
(classical.some_spec (well_ordering (nempty_imp_exists hS))).right m hm

-- Prove that the minimum is unique
-- I called it min_rw because it allows you to rw inside min, if the sets
-- are equal
theorem min_rw {S T : myset mynat} (hS : ¬myset.empty S) (hT : ¬myset.empty T)
(h : S = T) : min S hS = min T hT :=
begin
  apply mynat.le_antisymm; apply min_le,
    rw h,
  tactic.swap,
  rw ←h,
  all_goals { apply min_property, },
end

end hidden


-- Dos_por_a_igual_a_mas_a.lean
-- Si R es un anillo y a ∈ R, entonces 2 * a = a + a.
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 13-septiembre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si R es un anillo y a ∈ R, entonces
--    2 * a = a + a
-- ----------------------------------------------------------------------

import algebra.ring

variables {R : Type*} [ring R]
variables a : R

-- 1ª demostración
-- ===============

example : 2 * a = a + a :=
calc
  2 * a = (1 + 1) * a   : congr_fun (congr_arg has_mul.mul one_add_one_eq_two.symm) a
  ...   = 1 * a + 1 * a : add_mul 1 1 a
  ...   = a + 1 * a     : congr_arg (λ x, x + 1 * a) (one_mul a)
  ...   = a + a         : congr_arg (λ x, a + x) (one_mul a)

-- 2ª demostración
-- ===============

example : 2 * a = a + a :=
calc
  2 * a = (1 + 1) * a   : by rw one_add_one_eq_two
  ...   = 1 * a + 1 * a : by rw add_mul
  ...   = a + a         : by rw one_mul

-- 3ª demostración
-- ===============

example : 2 * a = a + a :=
by rw [one_add_one_eq_two.symm, add_mul, one_mul]

-- 4ª demostración
-- ===============

example : 2 * a = a + a :=
calc
  2 * a = (1 + 1)  * a  : rfl
  ...   = 1 * a + 1 * a : by simp [add_mul]
  ...   = a + a         : by simp

-- 5ª demostración
-- ===============

example : 2 * a = a + a :=
-- by library_search
two_mul a


/-
Copyright (c) 2019 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Benjamin Davidson
-/
import data.nat.parity

/-!
# Parity of integers

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

This file contains theorems about the `even` and `odd` predicates on the integers.

## Tags

even, odd
-/

namespace int

variables {m n : ℤ}

@[simp] theorem mod_two_ne_one : ¬ n % 2 = 1 ↔ n % 2 = 0 :=
by cases mod_two_eq_zero_or_one n with h h; simp [h]

local attribute [simp] -- euclidean_domain.mod_eq_zero uses (2 ∣ n) as normal form
theorem mod_two_ne_zero : ¬ n % 2 = 0 ↔ n % 2 = 1 :=
by cases mod_two_eq_zero_or_one n with h h; simp [h]

theorem even_iff : even n ↔ n % 2 = 0 :=
⟨λ ⟨m, hm⟩, by simp [← two_mul, hm], λ h, ⟨n / 2, (mod_add_div n 2).symm.trans
  (by simp [← two_mul, h])⟩⟩

theorem odd_iff : odd n ↔ n % 2 = 1 :=
⟨λ ⟨m, hm⟩, by { rw [hm, add_mod], norm_num },
 λ h, ⟨n / 2, (mod_add_div n 2).symm.trans (by { rw h, abel })⟩⟩

lemma not_even_iff : ¬ even n ↔ n % 2 = 1 :=
by rw [even_iff, mod_two_ne_zero]

lemma not_odd_iff : ¬ odd n ↔ n % 2 = 0 :=
by rw [odd_iff, mod_two_ne_one]

lemma even_iff_not_odd : even n ↔ ¬ odd n :=
by rw [not_odd_iff, even_iff]

@[simp] lemma odd_iff_not_even : odd n ↔ ¬ even n :=
by rw [not_even_iff, odd_iff]

lemma is_compl_even_odd : is_compl {n : ℤ | even n} {n | odd n} :=
by simp [← set.compl_set_of, is_compl_compl]

lemma even_or_odd (n : ℤ) : even n ∨ odd n :=
or.imp_right odd_iff_not_even.2 $ em $ even n

lemma even_or_odd' (n : ℤ) : ∃ k, n = 2 * k ∨ n = 2 * k + 1 :=
by simpa only [← two_mul, exists_or_distrib, ← odd, ← even] using even_or_odd n

lemma even_xor_odd (n : ℤ) : xor (even n) (odd n) :=
begin
  cases even_or_odd n with h,
  { exact or.inl ⟨h, even_iff_not_odd.mp h⟩ },
  { exact or.inr ⟨h, odd_iff_not_even.mp h⟩ },
end

lemma even_xor_odd' (n : ℤ) : ∃ k, xor (n = 2 * k) (n = 2 * k + 1) :=
begin
  rcases even_or_odd n with ⟨k, rfl⟩ | ⟨k, rfl⟩;
  use k,
  { simpa only [← two_mul, xor, true_and, eq_self_iff_true, not_true, or_false, and_false]
      using (succ_ne_self (2*k)).symm },
  { simp only [xor, add_right_eq_self, false_or, eq_self_iff_true, not_true, not_false_iff,
               one_ne_zero, and_self] },
end

@[simp] theorem two_dvd_ne_zero : ¬ 2 ∣ n ↔ n % 2 = 1 :=
even_iff_two_dvd.symm.not.trans not_even_iff

instance : decidable_pred (even : ℤ → Prop) := λ n, decidable_of_iff _ even_iff.symm
instance : decidable_pred (odd : ℤ → Prop) := λ n, decidable_of_iff _ odd_iff_not_even.symm

@[simp] theorem not_even_one : ¬ even (1 : ℤ) :=
by rw even_iff; norm_num

@[parity_simps] theorem even_add : even (m + n) ↔ (even m ↔ even n) :=
by cases mod_two_eq_zero_or_one m with h₁ h₁;
   cases mod_two_eq_zero_or_one n with h₂ h₂;
   simp [even_iff, h₁, h₂, int.add_mod];
   norm_num

theorem even_add' : even (m + n) ↔ (odd m ↔ odd n) :=
by rw [even_add, even_iff_not_odd, even_iff_not_odd, not_iff_not]

@[simp] theorem not_even_bit1 (n : ℤ) : ¬ even (bit1 n) :=
by simp [bit1] with parity_simps

lemma two_not_dvd_two_mul_add_one (n : ℤ) : ¬(2 ∣ 2 * n + 1) :=
by { simp [add_mod], refl }

@[parity_simps] theorem even_sub : even (m - n) ↔ (even m ↔ even n) :=
by simp [sub_eq_add_neg] with parity_simps

theorem even_sub' : even (m - n) ↔ (odd m ↔ odd n) :=
by rw [even_sub, even_iff_not_odd, even_iff_not_odd, not_iff_not]

@[parity_simps] theorem even_add_one : even (n + 1) ↔ ¬ even n :=
by simp [even_add]

@[parity_simps] theorem even_mul : even (m * n) ↔ even m ∨ even n :=
by cases mod_two_eq_zero_or_one m with h₁ h₁;
   cases mod_two_eq_zero_or_one n with h₂ h₂;
   simp [even_iff, h₁, h₂, int.mul_mod];
   norm_num

theorem odd_mul : odd (m * n) ↔ odd m ∧ odd n :=
by simp [not_or_distrib] with parity_simps

theorem odd.of_mul_left (h : odd (m * n)) : odd m :=
(odd_mul.mp h).1

theorem odd.of_mul_right (h : odd (m * n)) : odd n :=
(odd_mul.mp h).2

@[parity_simps] theorem even_pow {n : ℕ} : even (m ^ n) ↔ even m ∧ n ≠ 0 :=
by { induction n with n ih; simp [*, even_mul, pow_succ], tauto }

theorem even_pow' {n : ℕ} (h : n ≠ 0) : even (m ^ n) ↔ even m :=
even_pow.trans $ and_iff_left h

@[parity_simps] theorem odd_add : odd (m + n) ↔ (odd m ↔ even n) :=
by rw [odd_iff_not_even, even_add, not_iff, odd_iff_not_even]

theorem odd_add' : odd (m + n) ↔ (odd n ↔ even m) :=
by rw [add_comm, odd_add]

lemma ne_of_odd_add (h : odd (m + n)) : m ≠ n :=
λ hnot, by simpa [hnot] with parity_simps using h

@[parity_simps] theorem odd_sub : odd (m - n) ↔ (odd m ↔ even n) :=
by rw [odd_iff_not_even, even_sub, not_iff, odd_iff_not_even]

theorem odd_sub' : odd (m - n) ↔ (odd n ↔ even m) :=
by rw [odd_iff_not_even, even_sub, not_iff, not_iff_comm, odd_iff_not_even]

lemma even_mul_succ_self (n : ℤ) : even (n * (n + 1)) :=
begin
  rw even_mul,
  convert n.even_or_odd,
  simp with parity_simps
end

@[simp, norm_cast] theorem even_coe_nat (n : ℕ) : even (n : ℤ) ↔ even n :=
by rw_mod_cast [even_iff, nat.even_iff]

@[simp, norm_cast] theorem odd_coe_nat (n : ℕ) : odd (n : ℤ) ↔ odd n :=
by rw [odd_iff_not_even, nat.odd_iff_not_even, even_coe_nat]

@[simp] theorem nat_abs_even : even n.nat_abs ↔ even n :=
by simp [even_iff_two_dvd, dvd_nat_abs, coe_nat_dvd_left.symm]

@[simp] theorem nat_abs_odd : odd n.nat_abs ↔ odd n :=
by rw [odd_iff_not_even, nat.odd_iff_not_even, nat_abs_even]

alias nat_abs_even ↔ _ _root_.even.nat_abs
alias nat_abs_odd ↔ _ _root_.odd.nat_abs

attribute [protected] even.nat_abs odd.nat_abs

lemma four_dvd_add_or_sub_of_odd {a b : ℤ} (ha : odd a) (hb : odd b) : 4 ∣ a + b ∨ 4 ∣ a - b :=
begin
  obtain ⟨m, rfl⟩ := ha,
  obtain ⟨n, rfl⟩ := hb,
  obtain h|h := int.even_or_odd (m + n),
  { right,
    rw [int.even_add, ←int.even_sub] at h,
    obtain ⟨k, hk⟩ := h,
    convert dvd_mul_right 4 k,
    rw [eq_add_of_sub_eq hk, mul_add, add_assoc, add_sub_cancel, ← two_mul, ←mul_assoc],
    refl },
  { left,
    obtain ⟨k, hk⟩ := h,
    convert dvd_mul_right 4 (k + 1),
    rw [eq_sub_of_add_eq hk, add_right_comm, ←add_sub, mul_add, mul_sub, add_assoc, add_assoc,
      sub_add, add_assoc, ←sub_sub (2 * n), sub_self, zero_sub, sub_neg_eq_add, ←mul_assoc,
      mul_add],
    refl },
end

lemma two_mul_div_two_of_even : even n → 2 * (n / 2) = n :=
λ h, int.mul_div_cancel' (even_iff_two_dvd.mp h)

lemma div_two_mul_two_of_even : even n → n / 2 * 2 = n := --int.div_mul_cancel
λ h, int.div_mul_cancel (even_iff_two_dvd.mp h)

lemma two_mul_div_two_add_one_of_odd : odd n → 2 * (n / 2) + 1 = n :=
by { rintro ⟨c, rfl⟩, rw mul_comm, convert int.div_add_mod' _ _, simpa [int.add_mod] }

lemma div_two_mul_two_add_one_of_odd : odd n → n / 2 * 2 + 1 = n :=
by { rintro ⟨c, rfl⟩, convert int.div_add_mod' _ _, simpa [int.add_mod] }

lemma add_one_div_two_mul_two_of_odd : odd n → 1 + n / 2 * 2 = n :=
by { rintro ⟨c, rfl⟩, rw add_comm, convert int.div_add_mod' _ _, simpa [int.add_mod] }

lemma two_mul_div_two_of_odd (h : odd n) : 2 * (n / 2) = n - 1 :=
eq_sub_of_add_eq (two_mul_div_two_add_one_of_odd h)

-- Here are examples of how `parity_simps` can be used with `int`.

example (m n : ℤ) (h : even m) : ¬ even (n + 3) ↔ even (m^2 + m + n) :=
by simp [*, (dec_trivial : ¬ 2 = 0)] with parity_simps

example : ¬ even (25394535 : ℤ) :=
by simp

end int