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

import tactic -- imports all the Lean tactics
import solutions.section06quotients.sheet2zmod37

/-!

# Z/37 is a ring

(or an additive abelian group if you only get half way through the sheet)

-/

namespace Zmod

-- We already defined negation but right now it's called `neg`.
-- Let's set up notation for it so we can call it `-`.

instance : has_neg Zmod37 :=
{ neg := neg }

-- Now we can talk about `-⟦a⟧` and Lean will interpret it as `neg ⟦a⟧`.

-- Let's train the simplifier to push everything into
-- the brackets
@[simp] lemma neg_def (a : ℤ) : -⟦a⟧ = ⟦-a⟧ :=
begin
  refl
end

-- Let's also define notation for zero
instance : has_zero Zmod37 :=
{ zero := ⟦0⟧ }

@[simp] lemma zero_def : (0 : Zmod37) = ⟦0⟧ :=
begin
  refl
end

/-

## Maps X → X → X

We're trying to make `Zmod37` into an additive group; we've defined
the additive unit `0` and additive inverse `-`; we still need to
define the group law `+` though. The type of `+` will be
`Zmod37 → Zmod37 → Zmod37` and this might look a bit weird, so let
me just spell it out. We usually think of addition as a function
which eats two elements of an additive group `G` and returns another
element. But here what we're going to do is to think of addition
as a function `g + _` which eats *one* element `g` and returns a
function `G → G`, namely the function which adds `g` to its input.
With this way of thinking about it, addition is a function
from `Zmod37` to the type of functions from `Zmod37` to `Zmod37`,
and this latter type is called `Zmod37 → Zmod37`. So, putting
it all together, addition has type `Zmod37 → (Zmod37 → Zmod37)`,
and because `→` is right associative in Lean we can write this
as `Zmod37 → Zmod37 → Zmod37` (indeed, that's what "right associative" means).

-/

-- We define addition using a function in the library called `quotient.map₂`
-- which will descend a term of type `ℤ → ℤ → ℤ` to a term of type
-- `Zmod37 → Zmod37 → Zmod37`. We descend addition on the integers.
-- To make this work (to check it's a "well-defined definition") we need to prove
-- a theorem, hence this starts as a definition and ends up as a proof of a theorem.

def add : Zmod37 → Zmod37 → Zmod37 :=
quotient.map₂ (λ a b, a + b) begin
  -- keep intro-ing, and dsimp the lambdas away
  sorry
end

instance : has_add Zmod37 :=
{ add := add }

@[simp] lemma add_def (a b : ℤ) : ⟦a⟧ + ⟦b⟧ = ⟦a + b⟧ :=
begin
  refl
end

/-

## `quotient.induction_on`

The one thing I've not told you about is a really nice way of
reducing questions about `Zmod37` to equations about integers.
Sure you can use `surjective_quotient_mk` but there's a much
nicer way; you can do "induction on the terms of the quotient".
Let me do this first one for you, to give you the idea of
how to do these simply.

-/
lemma add_zero (z : Zmod37) : z + 0 = z :=
begin
  -- A question about Zmod37, a type we're in the middle of building
  -- an API for.
  apply quotient.induction_on z, clear z,
  -- Still a question about Zmod37, but now all our variables
  -- are integers,
  intro a,
  -- There are now two approaches. You can either `apply quotient.sound`
  -- to turn it into a question about integers, where we can use
  -- all the usual tactics like `ring`, or you can see how far
  -- the simplifier gets, because we've been training it to
  -- do questions like these.
  -- We told it `0 = ⟦0⟧` and `⟦a⟧ + ⟦b⟧ = ⟦a + b⟧`.
  -- In this case, the simplifier can do it.
  simp,
  -- If it can't do it, try applying `quotient.sound` and
  -- then perhaps `dsimp` to get rid of the lambdas.
end

-- I'll give you the first line for this one.
lemma add_comm (y z : Zmod37) : y + z = z + y :=
begin
  apply quotient.induction_on₂ y z, clear y z,
  sorry
end

-- See if you can prove the remaining axioms for an additive abelian group yourself.
-- We name this instance `Zmod.add_comm_group` and we'll use it later on.
instance add_comm_group : add_comm_group Zmod37 :=
{ add := (+),
  zero := 0,
  neg := has_neg.neg,
  add_assoc := begin
    sorry
  end,
  zero_add := begin
    sorry
  end,
  add_zero := add_zero,
  add_left_neg := begin
    sorry
  end,
  add_comm := add_comm }

-- Bonus points: see if you can make it a ring!
-- The rest of this file introduces no new techniques; all it offers
-- you is the satisfaction of proving that ℤ/37ℤ is a ring by yourself.

instance : has_one Zmod37 :=
{ one := ⟦1⟧ }

@[simp] lemma one_def : (1 : Zmod37) = ⟦1⟧ :=
begin
  refl
end

def mul : Zmod37 → Zmod37 → Zmod37 :=
quotient.map₂ (λ x y, x * y) begin
  -- tricky!
  sorry,
end

instance : has_mul Zmod37 :=
{ mul := mul }

@[simp] lemma mul_def (a b : ℤ) : ⟦a⟧ * ⟦b⟧ = ⟦a * b⟧ :=
begin
  refl
end

instance : comm_ring Zmod37 :=
{
  mul := (*),
  add := (+),
  mul_assoc := begin
    sorry
  end,
  one := 1,
  one_mul := begin
    sorry
  end,
  mul_one := begin
    sorry
  end,
  left_distrib := begin
    sorry,
  end,
  right_distrib := begin
    sorry,
  end,
  mul_comm := begin
    sorry,
end,
  -- the rest of the ring axioms are the axioms for an additive abelian group,
  -- and we did those already.
  ..Zmod.add_comm_group }
end Zmod


/-
Copyright (c) 2019 Minchao Wu. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Minchao Wu, Chris Hughes
-/
import data.list.basic
/-!
# Minimum and maximum of lists

## Main definitions

The main definitions are `argmax`, `argmin`, `minimum` and `maximum` for lists.

`argmax f l` returns `some a`, where `a` of `l` that maximises `f a`. If there are `a b` such that
  `f a = f b`, it returns whichever of `a` or `b` comes first in the list.
  `argmax f []` = none`

`minimum l` returns an `with_top α`, the smallest element of `l` for nonempty lists, and `⊤` for
`[]`
-/
namespace list
variables {α : Type*} {β : Type*} [linear_order β]

/-- Auxiliary definition to define `argmax` -/
def argmax₂ (f : α → β) (a : option α) (b : α) : option α :=
option.cases_on a (some b) (λ c, if f b ≤ f c then some c else some b)

/-- `argmax f l` returns `some a`, where `a` of `l` that maximises `f a`. If there are `a b` such
that `f a = f b`, it returns whichever of `a` or `b` comes first in the list.
`argmax f []` = none` -/
def argmax (f : α → β) (l : list α) : option α :=
l.foldl (argmax₂ f) none

/-- `argmin f l` returns `some a`, where `a` of `l` that minimises `f a`. If there are `a b` such
that `f a = f b`, it returns whichever of `a` or `b` comes first in the list.
`argmin f []` = none` -/
def argmin (f : α → β) (l : list α) :=
@argmax _ (order_dual β) _ f l

@[simp] lemma argmax_two_self (f : α → β) (a : α) : argmax₂ f (some a) a = a :=
if_pos le_rfl

@[simp] lemma argmax_nil (f : α → β) : argmax f [] = none := rfl

@[simp] lemma argmin_nil (f : α → β) : argmin f [] = none := rfl

@[simp] lemma argmax_singleton {f : α → β} {a : α} : argmax f [a] = some a := rfl

@[simp] lemma argmin_singleton {f : α → β} {a : α} : argmin f [a] = a := rfl

@[simp] lemma foldl_argmax₂_eq_none {f : α → β} {l : list α} {o : option α} :
  l.foldl (argmax₂ f) o = none ↔ l = [] ∧ o = none :=
list.reverse_rec_on l (by simp) $
  (assume tl hd, by simp [argmax₂];
    cases foldl (argmax₂ f) o tl; simp; try {split_ifs}; simp)

private theorem le_of_foldl_argmax₂ {f : α → β} {l} : Π {a m : α} {o : option α}, a ∈ l →
  m ∈ foldl (argmax₂ f) o l → f a ≤ f m :=
list.reverse_rec_on l
  (λ _ _ _ h, absurd h $ not_mem_nil _)
  begin
    intros tl _ ih _ _ _ h ho,
    rw [foldl_append, foldl_cons, foldl_nil, argmax₂] at ho,
    cases hf : foldl (argmax₂ f) o tl,
    { rw [hf] at ho,
      rw [foldl_argmax₂_eq_none] at hf,
      simp [hf.1, hf.2, *] at * },
    rw [hf, option.mem_def] at ho,
    dsimp only at ho,
    cases mem_append.1 h with h h,
    { refine le_trans (ih h hf) _,
      have := @le_of_lt _ _ (f val) (f m),
      split_ifs at ho;
      simp * at * },
    { split_ifs at ho;
      simp * at * }
  end

private theorem foldl_argmax₂_mem (f : α → β) (l) : Π (a m : α),
  m ∈ foldl (argmax₂ f) (some a) l → m ∈ a :: l :=
list.reverse_rec_on l (by simp [eq_comm])
  begin
    assume tl hd ih a m,
    simp only [foldl_append, foldl_cons, foldl_nil, argmax₂],
    cases hf : foldl (argmax₂ f) (some a) tl,
    { simp {contextual := tt} },
    { dsimp only, split_ifs,
      { -- `finish [ih _ _ hf]` closes this goal
        rcases ih _ _ hf with rfl | H,
        { simp only [mem_cons_iff, mem_append, mem_singleton, option.mem_def], tauto },
        { apply λ hm, or.inr (list.mem_append.mpr $ or.inl _),
          exact (option.mem_some_iff.mp hm ▸ H)} },
      { simp {contextual := tt} } }
  end

theorem argmax_mem {f : α → β} : Π {l : list α} {m : α}, m ∈ argmax f l → m ∈ l
| [] m       := by simp
| (hd::tl) m := by simpa [argmax, argmax₂] using foldl_argmax₂_mem f tl hd m

theorem argmin_mem {f : α → β} : Π {l : list α} {m : α}, m ∈ argmin f l → m ∈ l :=
@argmax_mem _ (order_dual β) _ _

@[simp] theorem argmax_eq_none {f : α → β} {l : list α} : l.argmax f = none ↔ l = [] :=
by simp [argmax]

@[simp] theorem argmin_eq_none {f : α → β} {l : list α} : l.argmin f = none ↔ l = [] :=
@argmax_eq_none _ (order_dual β) _ _ _

theorem le_argmax_of_mem {f : α → β} {a m : α} {l : list α} : a ∈ l → m ∈ argmax f l → f a ≤ f m :=
le_of_foldl_argmax₂

theorem argmin_le_of_mem {f : α → β} {a m : α} {l : list α} : a ∈ l → m ∈ argmin f l → f m ≤ f a:=
@le_argmax_of_mem _ (order_dual β) _ _ _ _ _

theorem argmax_concat (f : α → β) (a : α) (l : list α) : argmax f (l ++ [a]) =
  option.cases_on (argmax f l) (some a) (λ c, if f a ≤ f c then some c else some a) :=
by rw [argmax, argmax]; simp [argmax₂]

theorem argmin_concat (f : α → β) (a : α) (l : list α) : argmin f (l ++ [a]) =
  option.cases_on (argmin f l) (some a) (λ c, if f c ≤ f a then some c else some a) :=
@argmax_concat _ (order_dual β) _ _ _ _

theorem argmax_cons (f : α → β) (a : α) (l : list α) : argmax f (a :: l) =
  option.cases_on (argmax f l) (some a) (λ c, if f c ≤ f a then some a else some c) :=
list.reverse_rec_on l rfl $
  assume hd tl ih,  begin
    rw [← cons_append, argmax_concat, ih, argmax_concat],
    cases h : argmax f hd with m,
    { simp [h] },
    { simp [h], dsimp,
      by_cases ham : f m ≤ f a,
      { rw if_pos ham, dsimp,
        by_cases htlm : f tl ≤ f m,
        { rw if_pos htlm, dsimp,
          rw [if_pos (le_trans htlm ham), if_pos ham] },
        { rw if_neg htlm } },
      { rw if_neg ham, dsimp,
        by_cases htlm : f tl ≤ f m,
        { rw if_pos htlm, dsimp,
          rw if_neg ham },
        { rw if_neg htlm, dsimp,
          rw [if_neg (not_le_of_gt (lt_trans (lt_of_not_ge ham) (lt_of_not_ge htlm)))] } } }
  end

theorem argmin_cons (f : α → β) (a : α) (l : list α) : argmin f (a :: l) =
  option.cases_on (argmin f l) (some a) (λ c, if f a ≤ f c then some a else some c) :=
@argmax_cons _ (order_dual β) _ _ _ _

theorem index_of_argmax [decidable_eq α] {f : α → β} : Π {l : list α} {m : α}, m ∈ argmax f l →
  ∀ {a}, a ∈ l → f m ≤ f a → l.index_of m ≤ l.index_of a
| []       m _  _ _  _   := by simp
| (hd::tl) m hm a ha ham := begin
  simp only [index_of_cons, argmax_cons, option.mem_def] at ⊢ hm,
  cases h : argmax f tl,
  { rw h at hm,
    simp * at * },
  { rw h at hm,
    dsimp only at hm,
    cases ha with hahd hatl,
    { clear index_of_argmax,
      subst hahd,
      split_ifs at hm,
      { subst hm },
      { subst hm, contradiction } },
    { have := index_of_argmax h hatl, clear index_of_argmax,
      split_ifs at *;
      refl <|> exact nat.zero_le _ <|> simp [*, nat.succ_le_succ_iff, -not_le] at * } }
end

theorem index_of_argmin [decidable_eq α] {f : α → β} : Π {l : list α} {m : α}, m ∈ argmin f l →
  ∀ {a}, a ∈ l → f a ≤ f m → l.index_of m ≤ l.index_of a :=
@index_of_argmax _ (order_dual β) _ _ _

theorem mem_argmax_iff [decidable_eq α] {f : α → β} {m : α} {l : list α} :
  m ∈ argmax f l ↔ m ∈ l ∧ (∀ a ∈ l, f a ≤ f m) ∧
    (∀ a ∈ l, f m ≤ f a → l.index_of m ≤ l.index_of a) :=
⟨λ hm, ⟨argmax_mem hm, λ a ha, le_argmax_of_mem ha hm, λ _, index_of_argmax hm⟩,
  begin
    rintros ⟨hml, ham, hma⟩,
    cases harg : argmax f l with n,
    { simp * at * },
    { have := le_antisymm (hma n (argmax_mem harg) (le_argmax_of_mem hml harg))
        (index_of_argmax harg hml (ham _ (argmax_mem harg))),
      rw [(index_of_inj hml (argmax_mem harg)).1 this, option.mem_def] }
  end⟩

theorem argmax_eq_some_iff [decidable_eq α] {f : α → β} {m : α} {l : list α} :
  argmax f l = some m ↔ m ∈ l ∧ (∀ a ∈ l, f a ≤ f m) ∧
    (∀ a ∈ l, f m ≤ f a → l.index_of m ≤ l.index_of a) := mem_argmax_iff

theorem mem_argmin_iff [decidable_eq α] {f : α → β} {m : α} {l : list α} :
  m ∈ argmin f l ↔ m ∈ l ∧ (∀ a ∈ l, f m ≤ f a) ∧
    (∀ a ∈ l, f a ≤ f m → l.index_of m ≤ l.index_of a) :=
@mem_argmax_iff _ (order_dual β) _ _ _ _ _

theorem argmin_eq_some_iff [decidable_eq α] {f : α → β} {m : α} {l : list α} :
  argmin f l = some m ↔ m ∈ l ∧ (∀ a ∈ l, f m ≤ f a) ∧
    (∀ a ∈ l, f a ≤ f m → l.index_of m ≤ l.index_of a) := mem_argmin_iff

variable [linear_order α]

/-- `maximum l` returns an `with_bot α`, the largest element of `l` for nonempty lists, and `⊥` for
`[]`  -/
def maximum (l : list α) : with_bot α := argmax id l

/-- `minimum l` returns an `with_top α`, the smallest element of `l` for nonempty lists, and `⊤` for
`[]`  -/
def minimum (l : list α) : with_top α := argmin id l

@[simp] lemma maximum_nil : maximum ([] : list α) = ⊥ := rfl

@[simp] lemma minimum_nil : minimum ([] : list α) = ⊤ := rfl

@[simp] lemma maximum_singleton (a : α) : maximum [a] = a := rfl

@[simp] lemma minimum_singleton (a : α) : minimum [a] = a := rfl

theorem maximum_mem {l : list α} {m : α} : (maximum l : with_top α) = m → m ∈ l := argmax_mem

theorem minimum_mem {l : list α} {m : α} : (minimum l : with_bot α) = m → m ∈ l := argmin_mem

@[simp] theorem maximum_eq_none {l : list α} : l.maximum = none ↔ l = [] := argmax_eq_none

@[simp] theorem minimum_eq_none {l : list α} : l.minimum = none ↔ l = [] := argmin_eq_none

theorem minimum_le_of_mem {a m : α} {l : list α} : a ∈ l → (minimum l : with_top α) = m → m ≤ a :=
argmin_le_of_mem

theorem le_maximum_of_mem' {a : α} {l : list α} (ha : a ∈ l) : (a : with_bot α) ≤ maximum l :=
option.cases_on (maximum l) (λ _ h, absurd ha ((h rfl).symm ▸ not_mem_nil _))
  (λ m hm _, with_bot.coe_le_coe.2 $ hm _ rfl)
  (λ m, @le_maximum_of_mem _ _ _ m _ ha)
  (@maximum_eq_none _ _ l).1

theorem le_minimum_of_mem' {a : α} {l : list α} (ha : a ∈ l) : minimum l ≤ (a : with_top α) :=
@le_maximum_of_mem' (order_dual α) _ _ _ ha

theorem maximum_concat (a : α) (l : list α) : maximum (l ++ [a]) = max (maximum l) a :=
begin
  rw max_comm,
  simp only [maximum, argmax_concat, id],
  cases h : argmax id l,
  { rw [max_eq_left], refl, exact bot_le },
  change (coe : α → with_bot α) with some,
  rw [max_comm],
  simp [max_def]
end

theorem minimum_concat (a : α) (l : list α) : minimum (l ++ [a]) = min (minimum l) a :=
@maximum_concat (order_dual α) _ _ _

theorem maximum_cons (a : α) (l : list α) : maximum (a :: l) = max a (maximum l) :=
list.reverse_rec_on l (by simp [@max_eq_left (with_bot α) _ _ _ bot_le])
  (λ tl hd ih, by rw [← cons_append, maximum_concat, ih, maximum_concat, max_assoc])

theorem minimum_cons (a : α) (l : list α) : minimum (a :: l) = min a (minimum l) :=
@maximum_cons (order_dual α) _ _ _

theorem maximum_eq_coe_iff {m : α} {l : list α} :
  maximum l = m ↔ m ∈ l ∧ (∀ a ∈ l, a ≤ m) :=
begin
  unfold_coes,
  simp only [maximum, argmax_eq_some_iff, id],
  split,
  { simp only [true_and, forall_true_iff] {contextual := tt} },
  { simp only [true_and, forall_true_iff] {contextual := tt},
    intros h a hal hma,
    rw [le_antisymm hma (h.2 a hal)] }
end

theorem minimum_eq_coe_iff {m : α} {l : list α} :
  minimum l = m ↔ m ∈ l ∧ (∀ a ∈ l, m ≤ a) :=
@maximum_eq_coe_iff (order_dual α) _ _ _

section fold

variables {M : Type*} [canonically_linear_ordered_add_monoid M]

/-! Note: since there is no typeclass typeclass dual
to `canonically_linear_ordered_add_monoid α` we cannot express these lemmas generally for
`minimum`; instead we are limited to doing so on `order_dual α`. -/

lemma maximum_eq_coe_foldr_max_of_ne_nil (l : list M) (h : l ≠ []) :
  l.maximum = (l.foldr max ⊥ : M) :=
begin
  induction l with hd tl IH,
  { contradiction },
  { rw [maximum_cons, foldr, with_bot.coe_max],
    by_cases h : tl = [],
    { simp [h, -with_top.coe_zero] },
    { simp [IH h] } }
end

lemma minimum_eq_coe_foldr_min_of_ne_nil (l : list (order_dual M)) (h : l ≠ []) :
  l.minimum = (l.foldr min ⊤ : order_dual M) :=
maximum_eq_coe_foldr_max_of_ne_nil l h

lemma maximum_nat_eq_coe_foldr_max_of_ne_nil (l : list ℕ) (h : l ≠ []) :
  l.maximum = (l.foldr max 0 : ℕ) :=
maximum_eq_coe_foldr_max_of_ne_nil l h

lemma max_le_of_forall_le (l : list M) (n : M) (h : ∀ (x ∈ l), x ≤ n) :
  l.foldr max ⊥ ≤ n :=
begin
  induction l with y l IH,
  { simp },
  { specialize IH (λ x hx, h x (mem_cons_of_mem _ hx)),
    have hy : y ≤ n := h y (mem_cons_self _ _),
    simpa [hy] using IH }
end

lemma le_min_of_le_forall (l : list (order_dual M)) (n : (order_dual M))
  (h : ∀ (x ∈ l), n ≤ x) :
  n ≤ l.foldr min ⊤ :=
max_le_of_forall_le l n h

lemma max_nat_le_of_forall_le (l : list ℕ) (n : ℕ) (h : ∀ (x ∈ l), x ≤ n) :
  l.foldr max 0 ≤ n :=
max_le_of_forall_le l n h

end fold

end list


/-
Copyright (c) 2019 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.multiset.nodup
import Mathlib.data.list.nat_antidiagonal
import Mathlib.PostPort

namespace Mathlib

/-!
# The "antidiagonal" {(0,n), (1,n-1), ..., (n,0)} as a multiset.
-/

namespace multiset

namespace nat

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

/-- A pair (i,j) is contained in the antidiagonal of `n` if and only if `i+j=n`. -/
@[simp] theorem mem_antidiagonal {n : ℕ} {x : ℕ × ℕ} : x ∈ antidiagonal n ↔ prod.fst x + prod.snd x = n := sorry

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

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

/-- The antidiagonal of `n` does not contain duplicate entries. -/
@[simp] theorem nodup_antidiagonal (n : ℕ) : nodup (antidiagonal n) :=
  iff.mpr coe_nodup (list.nat.nodup_antidiagonal n)

@[simp] theorem antidiagonal_succ {n : ℕ} : antidiagonal (n + 1) = (0, n + 1) ::ₘ map (prod.map Nat.succ id) (antidiagonal n) := sorry


import MyNat.Addition
import MyNat.Multiplication
namespace MyNat
open MyNat

/-!

## Level 1: `zero_mul`

## Lemma

For all natural numbers `m`, we have ` 0 * m = 0. `
-/
lemma zero_mul (m : MyNat) : 0 * m = 0 := by
  induction m with
  | zero =>
    rw [zero_is_0]
    rw [mul_zero]
  | succ m ih =>
    rw [mul_succ]
    rw [ih]
    rw [add_zero]

/-!
Next up is [Multiplication Level 2](./Level2.lean.md).
-/


section
  variable (p q r : Prop)

  -- commutativity of ∧ and ∨
  example : p ∧ q ↔ q ∧ p := by
    apply Iff.intro
    . intro h
      apply And.intro
      . exact h.right
      . exact h.left
    . intro h
      apply And.intro
      . exact h.right
      . exact h.left

  example : p ∨ q ↔ q ∨ p := by
    apply Iff.intro
    . intro hpq
      apply Or.elim hpq
      . intro hp
        apply Or.inr
        exact hp
      . intro hq
        apply Or.inl
        exact hq
    . intro hqp
      cases hqp with
      | inl hq =>
        apply Or.inr
        exact hq
      | inr hp =>
        apply Or.inl
        exact hp

  -- associativity of ∧ and ∨
  example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := by
    apply Iff.intro
    . intro hpqr
      apply And.intro
      . exact hpqr.left.left
      . apply And.intro
        . exact hpqr.left.right
        . exact hpqr.right
    . intro hpqr
      apply And.intro
      . apply And.intro
        . exact hpqr.left
        . exact hpqr.right.left
      . exact hpqr.right.right

  example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := by
    apply Iff.intro
    . intro hpqr
      cases hpqr with
      | inl hpq =>
          cases hpq with
          | inl hp =>
              exact Or.inl hp
          | inr hq =>
              exact Or.inr $ Or.inl hq
      | inr hr =>
          exact Or.inr $ Or.inr hr
    . intro hpqr
      cases hpqr with
      | inl hp =>
          exact Or.inl $ Or.inl hp
      | inr hqr =>
          cases hqr with
          | inl hq =>
              exact Or.inl $ Or.inr hq
          | inr hr =>
              exact Or.inr hr

  -- distributivity
  example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by
    apply Iff.intro
    . intro h
      have hp := h.left
      have hqr := h.right
      cases hqr with
      | inl hq =>
        have hpq := And.intro hp hq
        exact Or.inl hpq
      | inr hr =>
        have hpr := And.intro hp hr
        exact Or.inr hpr
    . intro h
      apply And.intro
      . cases h with
        | inl hpq =>
          exact hpq.left
        | inr hpr =>
          exact hpr.left
      . cases h with
        | inl hpq =>
          have hq := hpq.right
          exact Or.inl hq
        | inr hpr =>
          have hr := hpr.right
          exact Or.inr hr

  example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := by
    apply Iff.intro
    . intro h
      apply And.intro
      . cases h with
        | inl hp =>
          exact Or.inl hp
        | inr hqr =>
          have hq := hqr.left
          exact Or.inr hq
      . cases h with
        | inl hp =>
          exact Or.inl hp
        | inr hqr =>
          have hr := hqr.right
          exact Or.inr hr
    . intro h
      have hpq := h.left
      have hpr := h.right
      cases hpq with
      | inl hp =>
        exact Or.inl hp
      | inr hq =>
        cases hpr with
        | inl hp =>
          apply Or.inl
          exact hp
        | inr hr =>
          have hqr := And.intro hq hr
          apply Or.inr
          exact hqr

  -- other properties
  example : (p → (q → r)) ↔ (p ∧ q → r) := by
    apply Iff.intro
    . intros hpqr hpq
      have hp := hpq.left
      have hq := hpq.right
      have hqr := hpqr hp
      have hr := hqr hq
      exact hr
    . intros hpqr hp hq
      have hpq := And.intro hp hq
      have hr := hpqr hpq
      exact hr

  example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := by
    apply Iff.intro
    case mp =>
      intro hpqr
      apply And.intro
      case left =>
        intro hp
        have hpq := Or.intro_left q hp
        have hr := hpqr hpq
        assumption
      case right =>
        intro hq
        have hpq := Or.intro_right p hq
        have hr := hpqr hpq
        assumption
    case mpr =>
      intros hprqr hpq
      have hpr := hprqr.left
      have hqr := hprqr.right
      cases hpq with
      | inl hp =>
        have hr := hpr hp
        assumption
      | inr hq =>
        have hr := hqr hq
        assumption

  example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := by
    apply Iff.intro
    . intro hnpq
      apply And.intro
      . intro hp
        have hpq := Or.intro_left q hp
        exact absurd hpq hnpq
      . intro hq
        have hpq := Or.intro_right p hq
        exact absurd hpq hnpq
    . intro hnpnq hpq
      cases hpq with
      | inl hp =>
        exact hnpnq.left hp
      | inr hq =>
        exact hnpnq.right hq

  example : ¬p ∨ ¬q → ¬(p ∧ q) := by
    intro hnpnq hpq
    cases hnpnq with
    | inl hnp =>
      exact hnp hpq.left
    | inr hnq =>
      exact hnq hpq.right

  example : ¬(p ∧ ¬p) := by
    intro hpnp
    have hnp := hpnp.right
    have hp := hpnp.left
    exact hnp hp

  example : p ∧ ¬q → ¬(p → q) := by
    intro hpnq hpq
    have hp := hpnq.left
    have hnq := hpnq.right
    have hq := hpq hp
    exact hnq hq

  example : ¬p → (p → q) := by
    intro hnp hp
    exact absurd hp hnp

  example : (¬p ∨ q) → (p → q) := by
    intro hnpq hp
    cases hnpq with
    | inl hnp =>
      exact absurd hp hnp
    | inr hq =>
      exact hq

  example : p ∨ False ↔ p := by
    apply Iff.intro
    . intro hpf
      cases hpf with
      | inl hp =>
        exact hp
      | inr hf =>
        exact False.elim hf
    . intro hp
      apply Or.inl
      exact hp

  example : p ∧ False ↔ False := by
    apply Iff.intro
    . intro hpf
      exact hpf.right
    . intro hf
      apply And.intro
      . exact False.elim hf
      . exact hf

  example : (p → q) → (¬q → ¬p) := by
    intro hpq hnq hp
    have hq := hpq hp
    exact hnq hq

end

section
  open Classical

  variable (p q r : Prop)

  example : (p → q ∨ r) → ((p → q) ∨ (p → r)) := by
    intro hpqr
    apply Or.elim (em q)
    . intro hq
      apply Or.inl
      intro
      exact hq
    . intro hnq
      apply Or.inr
      intro hp
      have hqr := hpqr hp
      cases hqr with
      | inl hq => contradiction
      | inr hr => assumption

  example : ¬(p ∧ q) → ¬p ∨ ¬q := by
    intro hnpq
    apply Or.elim (em p)
    . intro hp
      apply Or.inr
      intro hq
      have hpq := And.intro hp hq
      contradiction
    . intro hnp
      apply Or.inl
      assumption

  example : ¬(p → q) → p ∧ ¬q := by
    intro hnpq
    apply Or.elim (em p)
    . intro hp
      apply Or.elim (em q)
      . intro hq
        apply And.intro
        . assumption
        . have hpq : p → q := fun _ => hq
          contradiction
      . intro hnq
        apply And.intro  <;> assumption
    . intro hnp
      apply Or.elim (em q)
      . intro hq
        have hpq : p → q := fun _ => hq
        contradiction
      . intro
        have hpq : p → q := fun hp => absurd hp hnp
        contradiction

  example : (p → q) → (¬p ∨ q) := by
    intro hpq
    cases (em p) with
    | inl hp =>
      have hq := hpq hp
      apply Or.inr
      assumption
    | inr hq =>
      apply Or.inl
      assumption

  example : (¬q → ¬p) → (p → q) := by
    intro hnqnp hp
    cases (em q) with
    | inl hq =>
      assumption
    | inr hnq =>
      have hnp := hnqnp hnq
      contradiction

  example : p ∨ ¬p := by
    cases (em p) with
    | inl hp =>
      apply Or.inl
      assumption
    | inr hp =>
      apply Or.inr
      assumption

  example : (((p → q) → p) → p) := by
    intro hpqp
    cases (em p) with
    | inl hp  =>
      assumption
    | inr hnp =>
      have hpq : p → q := fun hp : p => absurd hp hnp
      have hp : p := hpqp hpq
      assumption

end

section
  variable (α : Type) (p q : α → Prop)

  example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) := by
    apply Iff.intro
    . intros hAxpxqx
      apply And.intro
      . intro x
        have hpxqx := hAxpxqx x
        exact hpxqx.left
      . intro x
        have hpxqx := hAxpxqx x
        exact hpxqx.right
    . intro h
      have hAxpx := h.left
      have hAxqx := h.right
      intro x
      have px := hAxpx x
      have qx := hAxqx x
      apply And.intro
      . exact px
      . exact qx

  example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) := by
    intros hAxpxqx hAxpx x
    have hpx := hAxpx x
    have hpxqx := hAxpxqx x
    have hqx := hpxqx hpx
    exact hqx

  example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x := by
    intros hAxpxAxqx x
    cases hAxpxAxqx with
    | inl hAxpx =>
      have hpx := hAxpx x
      apply Or.inl
      assumption
    | inr hAxqx =>
      have hqx := hAxqx x
      apply Or.inr
      assumption

end

section
  open Classical

  variable (α : Type) (p q : α → Prop)
  variable (r : Prop)

  example : α → ((∀ _ : α, r) ↔ r) := by
    intro hα
    apply Iff.intro
    . intro hαr
      exact hαr hα
    . intro hr
      intro
      assumption
end

section
  variable (men : Type) (barber : men)
  variable (shaves : men → men → Prop)

  example (h : ∀ x : men, shaves barber x ↔ ¬ shaves x x) : False := by
    have h1 := h barber
    have h2 := fun hbb => h1.mp hbb hbb
    have h3 := h1.mpr h2
    contradiction

end

section ex2

example (p q r : Prop) (hp :  p)
  : (p ∨ q ∨ r) ∧ (q ∨ p ∨ r) ∧ (q ∨ r ∨ p) := by
  repeat (constructor; repeat (first | apply Or.inl; assumption | apply Or.inr | assumption ))

end ex2


import .lovelib

/-! # LoVe Demo 13: Rational and Real Numbers

We review the construction of `ℚ` and `ℝ` as quotient types.

Our procedure to construct types with specific properties:

1. Create a new type that can represent all elements, but not necessarily in a
   unique manner.

2. Quotient this representation, equating elements that should be equal.

3. Define operators on the quotient type by lifting functions from the base
   type and prove that they are compatible with the quotient relation.

We used this approach in lecture 11 to construct `ℤ`. It can be used for `ℚ` and
`ℝ` as well. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/-! ## Rational Numbers

**Step 1:** A rational number is a number that can be expressed as a fraction
`n / d` of integers `n` and `d ≠ 0`: -/

structure fraction :=
(num           : ℤ)
(denom         : ℤ)
(denom_ne_zero : denom ≠ 0)

/-! The number `n` is called the numerator, and the number `d` is called the
denominator.

The representation of a rational number as a fraction is not unique—e.g.,
`1 / 2 = 2 / 4 = -1 / -2`.

**Step 2:** Two fractions `n₁ / d₁` and `n₂ / d₂` represent the same rational
number if the ratio between numerator and denominator are the same—i.e.,
`n₁ * d₂ = n₂ * d₁`. This will be our equivalence relation `≈` on fractions. -/

namespace fraction

@[instance] def setoid : setoid fraction :=
{ r     := λa b : fraction, num a * denom b = num b * denom a,
  iseqv :=
    begin
      repeat { apply and.intro },
      { intros a; refl },
      { intros a b h; cc },
      { intros a b c eq_ab eq_bc,
        apply int.eq_of_mul_eq_mul_right (denom_ne_zero b),
        cc }
    end }

lemma setoid_iff (a b : fraction) :
  a ≈ b ↔ num a * denom b = num b * denom a :=
by refl

/-! **Step 3:** Define `0 := 0 / 1`, `1 := 1 / 1`, addition, multiplication, etc.

    `n₁ / d₁ + n₂ / d₂`     := `(n₁ * d₂ + n₂ * d₁) / (d₁ * d₂)`
    `(n₁ / d₁) * (n₂ / d₂)` := `(n₁ * n₂) / (d₁ * d₂)`

Then show that they are compatible with `≈`. -/

def of_int (i : ℤ) : fraction :=
{ num           := i,
  denom         := 1,
  denom_ne_zero := by simp }

@[instance] def has_zero : has_zero fraction :=
{ zero := of_int 0 }

@[instance] def has_one : has_one fraction :=
{ one := of_int 1 }

@[instance] def has_add : has_add fraction :=
{ add := λa b : fraction,
    { num           := num a * denom b + num b * denom a,
      denom         := denom a * denom b,
      denom_ne_zero :=
        by apply mul_ne_zero; exact denom_ne_zero _ } }

@[simp] lemma add_num (a b : fraction) :
  num (a + b) = num a * denom b + num b * denom a :=
by refl

@[simp] lemma add_denom (a b : fraction) :
  denom (a + b) = denom a * denom b :=
by refl

lemma add_equiv_add {a a' b b' : fraction} (ha : a ≈ a')
    (hb : b ≈ b') :
  a + b ≈ a' + b' :=
begin
  simp [setoid_iff, add_denom, add_num] at *,
  calc  (num a * denom b + num b * denom a)
          * (denom a' * denom b')
      = num a * denom a' * denom b * denom b'
          + num b * denom b' * denom a * denom a' :
    by simp [add_mul, mul_add]; ac_refl
  ... = num a' * denom a * denom b * denom b'
          + num b' * denom b * denom a * denom a' :
    by simp [*]
  ... = (num a' * denom b' + num b' * denom a')
          * (denom a * denom b) :
    by simp [add_mul, mul_add]; ac_refl
end

@[instance] def has_neg : has_neg fraction :=
{ neg := λa : fraction,
    { num := - num a,
      ..a } }

@[simp] lemma neg_num (a : fraction) :
  num (- a) = - num a :=
by refl

@[simp] lemma neg_denom (a : fraction) :
  denom (- a) = denom a :=
by refl

lemma setoid_neg {a a' : fraction} (hab : a ≈ a') :
  - a ≈ - a' :=
by simp [setoid_iff] at hab ⊢; exact hab

@[instance] def has_mul : has_mul fraction :=
{ mul := λa b : fraction,
    { num           := num a * num b,
      denom         := denom a * denom b,
      denom_ne_zero :=
        mul_ne_zero (denom_ne_zero a) (denom_ne_zero b) } }

@[simp] lemma mul_num (a b : fraction) :
  num (a * b) = num a * num b :=
by refl

@[simp] lemma mul_denom (a b : fraction) :
  denom (a * b) = denom a * denom b :=
by refl

lemma setoid_mul {a a' b b' : fraction} (ha : a ≈ a')
    (hb : b ≈ b') :
  a * b ≈ a' * b' :=
by simp [setoid_iff] at ha hb ⊢; cc

@[instance] def has_inv : has_inv fraction :=
{ inv := λa : fraction,
    if ha : num a = 0 then
      0
    else
      { num           := denom a,
        denom         := num a,
        denom_ne_zero := ha } }

lemma inv_def (a : fraction) (ha : num a ≠ 0) :
  a⁻¹ =
  { num           := denom a,
    denom         := num a,
    denom_ne_zero := ha } :=
dif_neg ha

lemma inv_zero (a : fraction) (ha : num a = 0) :
  a⁻¹ = 0 :=
dif_pos ha

@[simp] lemma inv_num (a : fraction) (ha : num a ≠ 0) :
  num (a⁻¹) = denom a :=
by rw inv_def a ha

@[simp] lemma inv_denom (a : fraction) (ha : num a ≠ 0) :
  denom (a⁻¹) = num a :=
by rw inv_def a ha

lemma setoid_inv {a a' : fraction} (ha : a ≈ a') :
  a⁻¹ ≈ a'⁻¹ :=
begin
  cases' classical.em (num a = 0),
  case inl : ha0 {
    cases' classical.em (num a' = 0),
    case inl : ha'0 {
      simp [ha0, ha'0, inv_zero] },
    case inr : ha'0 {
      simp [ha0, ha'0, setoid_iff, denom_ne_zero] at ha,
      cc } },
  case inr : ha0 {
    cases' classical.em (num a' = 0),
    case inl : ha'0 {
      simp [setoid_iff, ha'0, denom_ne_zero] at ha,
      cc },
    case inr : ha'0 {
      simp [setoid_iff, ha0, ha'0] at ha ⊢,
      cc } }
end

end fraction

def rat : Type :=
quotient fraction.setoid

namespace rat

@[instance] def has_zero : has_zero rat :=
{ zero := ⟦0⟧ }

@[instance] def has_one : has_one rat :=
{ one := ⟦1⟧ }

@[instance] def has_add : has_add rat :=
{ add := quotient.lift₂ (λa b : fraction, ⟦a + b⟧)
    begin
      intros a b a' b' ha hb,
      apply quotient.sound,
      exact fraction.add_equiv_add ha hb
    end }

@[instance] def has_neg : has_neg rat :=
{ neg := quotient.lift (λa : fraction, ⟦- a⟧)
    begin
      intros a a' ha,
      apply quotient.sound,
      exact fraction.setoid_neg ha
    end }

@[instance] def has_mul : has_mul rat :=
{ mul := quotient.lift₂ (λa b : fraction, ⟦a * b⟧)
    begin
      intros a b a' b' ha hb,
      apply quotient.sound,
      exact fraction.setoid_mul ha hb
    end }

@[instance] def has_inv : has_inv rat :=
{ inv := quotient.lift (λa : fraction, ⟦a⁻¹⟧)
    begin
      intros a a' ha,
      apply quotient.sound,
      exact fraction.setoid_inv ha
    end }

end rat

/-! ### Alternative Definitions of `ℚ`

**Alternative 1:** Define `ℚ` as a subtype of `fraction`, with the requirement
that the denominator is positive and that the numerator and the denominator have
no common divisors except `1` and `-1`: -/

namespace alternative

def rat.is_canonical (a : fraction) : Prop :=
fraction.denom a > 0
∧ nat.coprime (int.nat_abs (fraction.num a))
  (int.nat_abs (fraction.denom a))

def rat : Type :=
{a : fraction // rat.is_canonical a}

end alternative

/-! This is more or less the `mathlib` definition.

Advantages:

* no quotient required;
* more efficient computation;
* more properties are syntactic equalities up to computation.

Disadvantage:

* more complicated function definitions.

### Real Numbers

Some sequences of rational numbers seem to converge because the numbers in the
sequence get closer and closer to each other, and yet do not converge to a
rational number.

Example:

    `a₀ = 1`
    `a₁ = 1.4`
    `a₂ = 1.41`
    `a₃ = 1.414`
    `a₄ = 1.4142`
    `a₅ = 1.41421`
    `a₆ = 1.414213`
    `a₇ = 1.4142135`
       ⋮

This sequence seems to converge because each `a_n` is at most `10^-n` away from
any of the following numbers. But the limit is `√2`, which is not a rational
number.

The rational numbers are incomplete, and the reals are their  __completion__.

To construct the reals, we need to fill in the gaps that are revealed by these
sequences that seem to converge, but do not.

Mathematically, a sequence `a₀, a₁, …` of rational numbers is __Cauchy__ if for
any `ε > 0`, there exists an `N ∈ ℕ` such that for all `m ≥ N`, we have
`|a_N - a_m| < ε`.

In other words, no matter how small we choose `ε`, we can always find a point in
the sequence from which all following numbers deviate less than by `ε`. -/

def is_cau_seq (f : ℕ → ℚ) : Prop :=
∀ε > 0, ∃N, ∀m ≥ N, abs (f N - f m) < ε

/-! Not every sequence is a Cauchy sequence: -/

lemma id_not_cau_seq :
  ¬ is_cau_seq (λn : ℕ, (n : ℚ)) :=
begin
  rw is_cau_seq,
  intro h,
  cases' h 1 zero_lt_one with i hi,
  have hi_succi :=
    hi (i + 1) (by simp),
  simp [←sub_sub] at hi_succi,
  assumption
end

/-! We define a type of Cauchy sequences as a subtype: -/

def cau_seq : Type :=
{f : ℕ → ℚ // is_cau_seq f}

def seq_of (f : cau_seq) : ℕ → ℚ :=
subtype.val f

/-! Cauchy sequences represent real numbers:

* `a_n = 1 / n` represents the real number `0`;
* `1, 1.4, 1.41, …` represents the real number `√2`;
* `a_n = 0` also represents the real number `0`.

Since different Cauchy sequences can represent the same real number, we need to
take the quotient. Formally, two sequences represent the same real number when
their difference converges to zero: -/

namespace cau_seq

@[instance] def setoid : setoid cau_seq :=
{ r     := λf g : cau_seq,
    ∀ε > 0, ∃N, ∀m ≥ N, abs (seq_of f m - seq_of g m) < ε,
  iseqv :=
    begin
      apply and.intro,
      { intros f ε hε,
        apply exists.intro 0,
        finish },
      apply and.intro,
      { intros f g hfg ε hε,
        cases' hfg ε hε with N hN,
        apply exists.intro N,
        intros m hm,
        rw abs_sub,
        apply hN m hm },
      { intros f g h hfg hgh ε hε,
        cases' hfg (ε / 2) (half_pos hε) with N₁ hN₁,
        cases' hgh (ε / 2) (half_pos hε) with N₂ hN₂,
        apply exists.intro (max N₁ N₂),
        intros m hm,
        calc  abs (seq_of f m - seq_of h m)
            ≤ abs (seq_of f m - seq_of g m)
              + abs (seq_of g m - seq_of h m) :
          by apply abs_sub_le
        ... < ε / 2 + ε / 2 :
          add_lt_add (hN₁ m (le_of_max_le_left hm))
            (hN₂ m (le_of_max_le_right hm))
        ... = ε :
          by simp }
    end }

lemma setoid_iff (f g : cau_seq) :
  f ≈ g ↔
  ∀ε > 0, ∃N, ∀m ≥ N, abs (seq_of f m - seq_of g m) < ε :=
by refl

/-! We can define constants such as `0` and `1` as a constant sequence. Any
constant sequence is a Cauchy sequence: -/

def const (q : ℚ) : cau_seq :=
subtype.mk (λ_ : ℕ, q) (by rw is_cau_seq; intros ε hε; finish)

/-! Defining addition of real numbers requires a little more effort. We define
addition on Cauchy sequences as pairwise addition: -/

@[instance] def has_add : has_add cau_seq :=
{ add := λf g : cau_seq,
    subtype.mk (λn : ℕ, seq_of f n + seq_of g n) sorry }

/-! Above, we omit the proof that the addition of two Cauchy sequences is again
a Cauchy sequence.

Next, we need to show that this addition is compatible with `≈`: -/

lemma add_equiv_add {f f' g g' : cau_seq} (hf : f ≈ f')
    (hg : g ≈ g') :
  f + g ≈ f' + g' :=
begin
  intros ε₀ hε₀,
  simp [setoid_iff],
  cases' hf (ε₀ / 2) (half_pos hε₀) with Nf hNf,
  cases' hg (ε₀ / 2) (half_pos hε₀) with Ng hNg,
  apply exists.intro (max Nf Ng),
  intros m hm,
  calc  abs (seq_of (f + g) m - seq_of (f' + g') m)
      = abs ((seq_of f m + seq_of g m)
           - (seq_of f' m + seq_of g' m)) :
    by refl
  ... = abs ((seq_of f m - seq_of f' m)
           + (seq_of g m - seq_of g' m)) :
    begin
      have arg_eq :
        seq_of f m + seq_of g m - (seq_of f' m + seq_of g' m) =
        seq_of f m - seq_of f' m + (seq_of g m - seq_of g' m),
        by linarith,
      rw arg_eq
    end
  ... ≤ abs (seq_of f m - seq_of f' m)
      + abs (seq_of g m - seq_of g' m) :
    by apply abs_add
  ... < ε₀ / 2 + ε₀ / 2 :
    add_lt_add (hNf m (le_of_max_le_left hm))
      (hNg m (le_of_max_le_right hm))
  ... = ε₀ :
    by simp
end

end cau_seq

/-! The real numbers are the quotient: -/

def real : Type :=
quotient cau_seq.setoid

namespace real

@[instance] def has_zero : has_zero real :=
{ zero := ⟦cau_seq.const 0⟧ }

@[instance] def has_one : has_one real :=
{ one := ⟦cau_seq.const 1⟧ }

@[instance] def has_add : has_add real :=
{ add := quotient.lift₂ (λa b : cau_seq, ⟦a + b⟧)
    begin
      intros a b a' b' ha hb,
      apply quotient.sound,
      exact cau_seq.add_equiv_add ha hb,
    end }

end real

/-! ### Alternative Definitions of `ℝ`

* Dedekind cuts: `r : ℝ` is represented essentially as `{x : ℚ | x < r}`.

* Binary sequences `ℕ → bool` can represent the interval `[0, 1]`. This can be
  used to build `ℝ`. -/

end LoVe


import .myRing
import .axiom_set
-- import tactic
open myRing
variables {R : Type} [myRing R]
variables {O : Type} [ordered_ring O ]

theorem div_self : ∀ (a  :R), a  ∣ a :=
begin
 intros a,
 rw divs,
 have : a*1=a := begin
  exact mul_one a,
 end,
 split,
  exact this ,

end
theorem div_one: ∀ (a :R), 1 ∣ a := begin
intro a,
rw divs,
use a,
rw one_mul,
end

theorem div_mul : ∀ (a b c :R), a ∣ b → a ∣  b*c := begin
intros a b c d,
rw divs at d,
cases d,
rw divs,
split, {
  have  :  a * d_w *c = b *c := begin
  exact mul_right c d_h,
  end,
  rw mul_assoc at this,
exact this,
},
end

theorem divs_add: ∀ ( a b k :R ),  k ∣ a → k ∣ b → k ∣ (a+b) := begin
intros a b c d e,
rw divs at d,
rw divs at e,
cases d,
cases e,
rw divs,
split, {
 have :  c* d_w  + c*e_w = a +b := begin
 rw d_h,
 rw e_h,
 end,
 rw ← mul_add at this,
 exact this,
},
end

theorem divs_linear_combination: ∀{a b c: R}, ∀(d e:R ), a ∣ b → a ∣ c → a ∣ (b*d+c*e) := begin
intros a b c d e f g,
  have o := div_mul a b d f,
  have t := div_mul a c e g,
  have w := divs_add  (b * d) (c*e) a o t ,
  exact w,
end

theorem divs_trans : ∀ (a b c : R), a ∣ b → b ∣ c → a ∣ c := begin
intros a b c d e,
rw divs at d,
rw divs at e,
cases d,
cases e,
rw divs,
split, {
rw ← d_h at e_h,
rw mul_assoc at e_h,
exact e_h,
},
end

-- theorem divs_le: ∀ (a b : O), is_positive (a) → is_positive (b) → a ∣ b → a ≤ b := begin
-- intros a b c d e,
-- rw divs at e,
-- cases e,
-- rw less_eq,
-- end

theorem neg_one_mul_neg_one: (-1:R) * (-1:R) = 1 := begin
 simp,
--  rw mul_one,
end


/-
Copyright (c) 2014 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn, Jeremy Avigad
-/
import data.nat.order.basic

/-!
#  Distance function on ℕ

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

This file defines a simple distance function on naturals from truncated subtraction.
-/

namespace nat

/-- Distance (absolute value of difference) between natural numbers. -/
def dist (n m : ℕ) := (n - m) + (m - n)

theorem dist.def (n m : ℕ) : dist n m = (n - m) + (m - n) := rfl

theorem dist_comm (n m : ℕ) : dist n m = dist m n :=
by simp [dist.def, add_comm]

@[simp] theorem dist_self (n : ℕ) : dist n n = 0 :=
by simp [dist.def, tsub_self]

theorem eq_of_dist_eq_zero {n m : ℕ} (h : dist n m = 0) : n = m :=
have n - m = 0, from nat.eq_zero_of_add_eq_zero_right h,
have n ≤ m, from tsub_eq_zero_iff_le.mp this,
have m - n = 0, from nat.eq_zero_of_add_eq_zero_left h,
have m ≤ n, from tsub_eq_zero_iff_le.mp this,
le_antisymm ‹n ≤ m› ‹m ≤ n›

theorem dist_eq_zero {n m : ℕ} (h : n = m) : dist n m = 0 :=
begin rw [h, dist_self] end

theorem dist_eq_sub_of_le_right {n m : ℕ} (h : m ≤ n) : dist n m = n - m :=
begin rw [dist_comm], apply dist_eq_sub_of_le h end

theorem dist_tri_left (n m : ℕ) : m ≤ dist n m + n :=
le_trans le_tsub_add (add_le_add_right (nat.le_add_left _ _) _)

theorem dist_tri_right (n m : ℕ) : m ≤ n + dist n m :=
by rw add_comm; apply dist_tri_left

theorem dist_tri_left' (n m : ℕ) : n ≤ dist n m + m :=
by rw dist_comm; apply dist_tri_left

theorem dist_tri_right' (n m : ℕ) : n ≤ m + dist n m :=
by rw dist_comm; apply dist_tri_right

theorem dist_zero_right (n : ℕ) : dist n 0 = n :=
eq.trans (dist_eq_sub_of_le_right (zero_le n)) (tsub_zero n)

theorem dist_zero_left (n : ℕ) : dist 0 n = n :=
eq.trans (dist_eq_sub_of_le (zero_le n)) (tsub_zero n)

theorem dist_add_add_right (n k m : ℕ) : dist (n + k) (m + k) = dist n m :=
calc
  dist (n + k) (m + k) = ((n + k) - (m + k)) + ((m + k)-(n + k)) : rfl
                   ... = (n - m) + ((m + k) - (n + k))   : by rw add_tsub_add_eq_tsub_right
                   ... = (n - m) + (m - n)               : by rw add_tsub_add_eq_tsub_right

theorem dist_add_add_left (k n m : ℕ) : dist (k + n) (k + m) = dist n m :=
begin rw [add_comm k n, add_comm k m], apply dist_add_add_right end

theorem dist_eq_intro {n m k l : ℕ} (h : n + m = k + l) : dist n k = dist l m :=
calc
  dist n k = dist (n + m) (k + m) : by rw dist_add_add_right
       ... = dist (k + l) (k + m) : by rw h
       ... = dist l m             : by rw dist_add_add_left

theorem dist.triangle_inequality (n m k : ℕ) : dist n k ≤ dist n m + dist m k :=
have dist n m + dist m k = (n - m) + (m - k) + ((k - m) + (m - n)),
  by simp [dist.def, add_comm, add_left_comm],
by { rw [this, dist.def], exact add_le_add tsub_le_tsub_add_tsub tsub_le_tsub_add_tsub }

theorem dist_mul_right (n k m : ℕ) : dist (n * k) (m * k) = dist n m * k :=
by rw [dist.def, dist.def, right_distrib, tsub_mul, tsub_mul]

theorem dist_mul_left (k n m : ℕ) : dist (k * n) (k * m) = k * dist n m :=
by rw [mul_comm k n, mul_comm k m, dist_mul_right, mul_comm]

-- TODO(Jeremy): do when we have max and minx
--theorem dist_eq_max_sub_min {i j : nat} : dist i j = (max i j) - min i j :=
--sorry
/-
or.elim (lt_or_ge i j)
  (assume : i < j,
    by rw [max_eq_right_of_lt this, min_eq_left_of_lt this, dist_eq_sub_of_lt this])
  (assume : i ≥ j,
    by rw [max_eq_left this , min_eq_right this, dist_eq_sub_of_le_right this])
-/

theorem dist_succ_succ {i j : nat} : dist (succ i) (succ j) = dist i j :=
by simp [dist.def, succ_sub_succ]

theorem dist_pos_of_ne {i j : nat} : i ≠ j → 0 < dist i j :=
assume hne, nat.lt_by_cases
  (assume : i < j,
     begin rw [dist_eq_sub_of_le (le_of_lt this)], apply tsub_pos_of_lt this end)
  (assume : i = j, by contradiction)
  (assume : i > j,
     begin rw [dist_eq_sub_of_le_right (le_of_lt this)], apply tsub_pos_of_lt this end)

end nat


/-
  Dependent types

  In class, I presented "vector α n" and "matrix α m n" as examples of
  dependent types. Make up some operations on these types -- like
  "vector_add" or "vector_reverse" -- and declare constants of the
  right type to describe them. Use implicit arguments for parameters
  that can be inferred. Then use "check" to make sure they work.

  You can make up other dependent types if you would like. Don't
  worry about the definitions; just declare constants with the right
  types.
-/

universe variable u

constant vector : Type u → ℕ → Type u
constant matrix : Type u → ℕ → ℕ → Type u

section
  variable α : Type u
  variables m n : ℕ

  check vector α n
  check matrix α m n

  variables v₁ v₂ : vector α n
  variables v₃ v₄ : vector α m
end

/-
  Propositional logic

  Do at least five of these, and satisfy the following constraints:
  - make sure you use introduction and elimination rules for all the
    connectives
  - do at least one that requires classical logic
  - prove ¬(p ↔ ¬p) without using classical logic
-/

section
  open classical

  variables p q r s : Prop

  -- commutativity of ∧ and ∨
  example : p ∧ q ↔ q ∧ p := sorry
  example : p ∨ q ↔ q ∨ p := sorry

  -- associativity of ∧ and ∨
  example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := sorry
  example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := sorry

  -- distributivity
  -- this next one is in *Theorem Proving in Lean*
  -- example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := sorry
  example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := sorry

  -- other properties
  example : (p → (q → r)) ↔ (p ∧ q → r) := sorry
  example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := sorry
  example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := sorry
  example : ¬p ∨ ¬q → ¬(p ∧ q) := sorry
  example : ¬(p ∧ ¬ p) := sorry
  example : p ∧ ¬q → ¬(p → q) := sorry
  example : ¬p → (p → q) := sorry
  example : (¬p ∨ q) → (p → q) := sorry
  example : p ∨ false ↔ p := sorry
  example : p ∧ false ↔ false := sorry
  example : ¬(p ↔ ¬p) := sorry
  example : (p → q) → (¬q → ¬p) := sorry

  -- these require classical reasoning
  example : (p → r ∨ s) → ((p → r) ∨ (p → s)) := sorry
  example : ¬(p ∧ q) → ¬p ∨ ¬q := sorry
  example : ¬(p → q) → p ∧ ¬q := sorry
  example : (p → q) → (¬p ∨ q) := sorry
  example : (¬q → ¬p) → (p → q) := sorry
  example : p ∨ ¬p := sorry
  example : (((p → q) → p) → p) := sorry
end

/-
  Writing mathematical assertions

  I put the definitions of "divides" and "pow" in a funny namespace
  here, to avoid conflicts with similarly-named definitions elsewhere
  in the library (though at the moment though would not pose any such
  conflicts). Don't worry about the recursive definition of pow or the
  "instance" declaration -- we will discuss those later.

  Fill in the definitions and statements indicated; you can use
  Wikipedia to look them up.
-/

namespace experiment

def divides (m n : ℕ) : Prop := ∃ k, m * k = n

instance : has_dvd nat := ⟨divides⟩

def pow (m : ℕ) : ℕ → ℕ
| 0     := 1
| (n+1) := m * pow n

infix `^` := pow

section
  variables m n : ℕ

  check m ∣ n     -- use \| to type the divides symbol
  check dvd m n   -- this is equivalent
  check m^n
  check pow m n

  vm_eval 2^10
  vm_eval 2^100
  vm_eval 10^2
end

def even (n : ℕ) : Prop := 2 ∣ n

def prime (n : ℕ) : Prop := sorry

-- Without any parameters, an expression of type Prop is an
-- assertion. The next one should be the statement that there are
-- infinitely many primes. You can say this by asserting that for
-- every natural number n, there is a prime number bigger than n.
def infinitely_many_primes : Prop := sorry

def Fermat_prime (n : ℕ) : Prop := sorry

def infinitely_many_Fermat_primes : Prop := sorry

def goldbach_conjecture : Prop := sorry

-- This is the statement that every odd number greater than 5 is the
-- sum of three primes.
def Goldbach's_weak_conjecture : Prop := sorry

def Fermat's_last_theorem : Prop := sorry

end experiment

/-
  Reasoning with quantifiers:

  Do at least three of these, including
  - at least one involving the universal quantifier
  - at least one involving the existential quantifier
  - at least one that requires classical logic
-/

section

variable  α : Type
variables (p q : α → Prop) (r : Prop)
variable  a : α

example : (∀ x, p x ∧ q x) ↔ (∀ x, p x) ∧ (∀ x, q x) := sorry
example : (∀ x, p x → q x) → (∀ x, p x) → (∀ x, q x) := sorry
example : (∀ x, p x) ∨ (∀ x, q x) → ∀ x, p x ∨ q x := sorry

example : (∃ x : α, r) → r := sorry
example : r → (∃ x : α, r) := sorry
example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r := sorry
-- in *Theorem Proving in Lean*
-- 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
-- in *Theorem Proving in Lean*
-- example : (∃ x, p x → r) ↔ (∀ x, p x) → r := sorry
example : (∃ x, r → p x) ↔ (r → ∃ x, p x) := sorry

end


/-
Copyright (c) 2021 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson
-/
import set_theory.cardinal.basic

/-!
# Finite Cardinality Functions

## Main Definitions

* `nat.card α` is the cardinality of `α` as a natural number.
  If `α` is infinite, `nat.card α = 0`.
* `enat.card α` is the cardinality of `α` as an extended natural number.
  If `α` is infinite, `enat.card α = ⊤`.

-/

open cardinal
noncomputable theory

variables {α β : Type*}

namespace nat

/-- `nat.card α` is the cardinality of `α` as a natural number.
  If `α` is infinite, `nat.card α = 0`. -/
protected def card (α : Type*) : ℕ := (mk α).to_nat

@[simp]
lemma card_eq_fintype_card [fintype α] : nat.card α = fintype.card α := mk_to_nat_eq_card

@[simp]
lemma card_eq_zero_of_infinite [infinite α] : nat.card α = 0 := mk_to_nat_of_infinite

lemma card_congr (f : α ≃ β) : nat.card α = nat.card β :=
cardinal.to_nat_congr f

lemma card_eq_of_bijective (f : α → β) (hf : function.bijective f) : nat.card α = nat.card β :=
card_congr (equiv.of_bijective f hf)

lemma card_eq_of_equiv_fin {α : Type*} {n : ℕ}
  (f : α ≃ fin n) : nat.card α = n :=
by simpa using card_congr f

/-- If the cardinality is positive, that means it is a finite type, so there is
an equivalence between `α` and `fin (nat.card α)`. See also `finite.equiv_fin`. -/
def equiv_fin_of_card_pos {α : Type*} (h : nat.card α ≠ 0) :
  α ≃ fin (nat.card α) :=
begin
  casesI fintype_or_infinite α,
  { simpa using fintype.equiv_fin α },
  { simpa using h },
end

lemma card_of_subsingleton (a : α) [subsingleton α] : nat.card α = 1 :=
begin
  rw [card_eq_fintype_card],
  convert fintype.card_of_subsingleton a,
end

@[simp] lemma card_unique [unique α] : nat.card α = 1 :=
card_of_subsingleton default

lemma card_eq_one_iff_unique : nat.card α = 1 ↔ subsingleton α ∧ nonempty α :=
cardinal.to_nat_eq_one_iff_unique

theorem card_of_is_empty [is_empty α] : nat.card α = 0 := by simp

@[simp] lemma card_prod (α β : Type*) : nat.card (α × β) = nat.card α * nat.card β :=
by simp only [nat.card, mk_prod, to_nat_mul, to_nat_lift]

@[simp] lemma card_ulift (α : Type*) : nat.card (ulift α) = nat.card α :=
card_congr equiv.ulift

@[simp] lemma card_plift (α : Type*) : nat.card (plift α) = nat.card α :=
card_congr equiv.plift

end nat

namespace enat

/-- `enat.card α` is the cardinality of `α` as an extended natural number.
  If `α` is infinite, `enat.card α = ⊤`. -/
def card (α : Type*) : enat := (mk α).to_enat

@[simp]
lemma card_eq_coe_fintype_card [fintype α] : card α = fintype.card α := mk_to_enat_eq_coe_card

@[simp]
lemma card_eq_top_of_infinite [infinite α] : card α = ⊤ := mk_to_enat_of_infinite

end enat


set_option trace.simplify.rewrite true

open list(nil)

constant α : Type
constants reverse reverse2 : list α → list α
constant append : list α → α → list α

axiom append.base (x : α) : append nil x = [x]
axiom append.step (x y : α) (xs : list α) :
  append (x :: xs) y = x :: append xs y

axiom reverse.base : reverse nil = nil
axiom reverse.step (x : α) (xs : list α) :
  reverse (x :: xs) = append (reverse xs) x

attribute [simp] append.base append.step reverse.base reverse.step

example (x y z : α) : append [x, y] z = [x, y, z] :=
begin
  rw append.step,
  rw append.step,
  rw append.base,
end

lemma reverse.single (x : α) : reverse [x] = [x] :=
begin
  -- simp, give the same steps for proof
  rw reverse.step,
  rw reverse.base,
  rw append.base,
end

lemma reverse_twice.single (x : α) : reverse (reverse [x]) = [x] :=
begin
  rw reverse.step,
  rw reverse.base,
  rw append.base,
  rw reverse.step,
  rw reverse.base,
  rw append.base,
end

lemma reverse_twice.two (x y : α) : reverse (reverse [x, y]) = [x, y] :=
begin
  rw reverse.step,
  rw reverse.step,
  rw reverse.base,
  rw append.base,
  -- ⊢ reverse (append [y] x) = [x, y] ~
  -- reverse (append xs x) = x :: reverse xs
  rw append.step,
  rw reverse.step,
  rw append.base,
  rw reverse.step,
  rw reverse.base,
  rw append.base,
  rw append.step,
  rw append.base,
end

-- reverse (y :: xs) = append (reverse xs) y
example (x y : α) (xs : list α) :
  reverse (x :: y :: xs) = append (append (reverse xs) y) x :=
begin
  rw reverse.step,
  rw reverse.step,
  -- rw ←reverse.step,
  -- rw ←reverse.step,
end

lemma reverse_right (x : α) (xs : list α) :
  reverse (append xs x) = x :: reverse xs :=
begin
  induction xs with head tail ih,
  case nil {
    -- simp,
    rw append.base,
    rw reverse.step,
    rw reverse.base,
    rw append.base,
  },
  case cons {
    -- simp [ih],
    -- 1st way:
  -- [append.step]: append (head :: tail) x ==> head :: append tail x
  -- [reverse.step]: reverse (head :: append tail x) ==> append (reverse (append tail x)) head
  -- [ih]: reverse (append tail x) ==> x :: reverse tail
  -- [append.step]: append (x :: reverse tail) head ==> x :: append (reverse tail) head
  -- [reverse.step]: reverse (head :: tail) ==> append (reverse tail) head
    rw append.step,
    rw reverse.step,
    rw ih,
    rw append.step,
    rw reverse.step, -- rw ←reverse.step, also works because equality

    -- 2nd way:
    -- rw reverse.step,
    -- rw append.step,
    -- rw reverse.step,
    -- rw ←append.step,
    -- congr,
    -- exact ih,
  },
end

lemma reverse_twice.induction_step (x : α) (xs : list α) :
  reverse (reverse xs) = xs →  reverse (reverse (x :: xs)) = x :: xs :=
begin
  intros h,
  rw reverse.step,
  rw reverse_right,
  congr,
  exact h,
end

theorem reverse_twice (xs : list α) : reverse (reverse xs) = xs :=
begin
  induction xs with head tail ih,
  repeat { rw reverse.base, },
  simp [ih, reverse_right],
  -------------------------
  -- rw reverse.step,
  -- rw reverse_right,
  -- rw ih,
  -------------------------
  -- rw reverse.step,
  -- have h : ∃ (y : α) (ys : list α), y :: ys = tail, sorry,
  -- rw ←h,

  -- rw ←reverse.step at ih,
  -- rw reverse.step,
  -- have h : ∀ (l1 l2 : list α) (x : α), l1 = l2 → x :: l1 = x :: l2, sorry,
  -- have H := h (reverse (reverse tail)) tail head,
  -- have ih2 := H ih,
  -- apply eq.trans _ ih2,
  -- rw append.step,
end

#print reverse_twice
-- #eval cons 1 nil -- [1]

-- how to prove without lemma reverse_right:
theorem reverse_twice₂ (xs : list α) : reverse (reverse xs) = xs :=
begin
  induction xs with y ys ih,
  repeat { rw reverse.base },
  -- rw reverse.step,
  induction ys with z zs ih2, -- I think two induction steps need to prove without lemma reverse_right, because we have one in reverse_twice proof and have another in reverse_right proof.
  case nil {
    simp [reverse.base, append.base, reverse.step],
  },
  case cons {
    rw reverse.step,
    -- rw reverse_right,
    -- have rev_right := reverse_right y (reverse (z :: zs)),
    have rev_right : reverse (append (reverse (z :: zs)) y) =
                       y :: reverse (reverse (z :: zs)),
      {
        induction (reverse (z :: zs)) with head tail ih3,
        {
          rw append.base,
          rw reverse.base,
          exact reverse.single y,
        },
        {
          rw append.step,
          rw reverse.step,
          rw ih3,
          rw append.step,
          congr,
          rw reverse.step,
        },
      },
    rw rev_right,
    rw ih,
  },
end

example (β : Type) [decidable_eq β] (x : β) (xs : list β) :
  (x :: xs) ∩ xs = xs :=
begin
  change (list.inter (x :: xs) xs = xs),
  simp [list.inter],
  induction xs with head tail ih,
  simp,
  simp,
  sorry,
end

example (xs : list α) : list.append xs nil = xs :=
begin
  sorry,
end


/-
Copyright (c) 2022 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa, Eric Wieser
-/
import algebra.char_p.basic

/-! # `char_p R 0` and `char_zero R` need not coincide for semirings

For rings, the two notions coincide.

In fact, `char_p.of_char_zero` shows that `char_zero R` implies `char_p R 0` for any `char_zero`
`add_monoid R` with `1`.
The reverse implication holds for any `add_left_cancel_monoid R` with `1`, by `char_p_to_char_zero`.

This file shows that there are semiring `R` for which `char_p R 0` holds and `char_zero R` does not.

The example is `{0, 1}` with saturating addition.
-/

@[simp] lemma add_one_eq_one (x : with_zero unit) : x + 1 = 1 :=
with_zero.cases_on x (by refl) (λ h, by refl)

lemma with_zero_unit_char_p_zero : char_p (with_zero unit) 0 :=
⟨λ x, by cases x; simp⟩

lemma with_zero_unit_not_char_zero : ¬ char_zero (with_zero unit) :=
λ ⟨h⟩, h.ne (by simp : 1 + 1 ≠ 0 + 1) (by simp)


theorem Ex005(a b : Prop): (a ∧ (a → ¬a)) → (a ∧ ¬a) :=
assume H:(a ∧ (a → ¬a)),
  have A:a, from and.elim_left H,
  have B:(a → ¬a), from and.elim_right H,
  have C:¬a,from B A,
  show a ∧ ¬a, from and.intro A C


-- ignore this part: we are building the notion of a set
-- go to line 36

inductive zfc : Type
| empty  : zfc
| insert : zfc → zfc → zfc

instance : has_emptyc zfc := ⟨zfc.empty⟩
instance : has_insert zfc zfc := ⟨zfc.insert⟩

instance : has_zero zfc := ⟨{}⟩
instance : has_one zfc := ⟨{0}⟩

def succ (a : zfc) : zfc := has_insert.insert a a

def zfc_add : zfc → zfc → zfc
| m 0 := m
| m (zfc.insert n p) := succ (zfc_add m n)

instance : has_add zfc := ⟨zfc_add⟩

inductive member : zfc → zfc → Prop
| left {a b c : zfc} : member a c → member a (has_insert.insert b c)
| right {a b : zfc} : member a (has_insert.insert a b)

instance : has_mem zfc zfc := ⟨member⟩
instance : has_subset zfc := ⟨(λ a b:zfc, (∀ z:zfc, z ∈ a → z ∈ b))⟩
infix `⊈`:50 := (λ a b, ¬(a ⊆ b))
infix `⊉`:50 := (λ a b, ¬(b ⊆ a))

axiom member.ext (a b : zfc) : (∀ x, x ∈ a ↔ x ∈ b) ↔ a=b

export zfc
export member

-- start here

/-

USAGE:
1. ¬P is shorthand of (P → false).
2. A ∉ B is shorthand of ¬(A ∈ B), which is shorthand of (A ∈ B → false) per 1.
3. A ⊆ B is shorthand of (∀ z, z ∈ A → z ∈ B).
4. A ⊈ B is shorthand of ¬(A ⊆ B), and then use 1 and 3.
5. A ⊇ B is shorthand of B ⊆ A.
6. If the current goal is x ∈ {a,b,c,d,e}, then using "left" or "apply left"
   will make the goal x ∈ {a,b,c,d}.
7. If the current goal is x ∈ {a,b,c,d,x}, then using "right" or "apply right"
   or "exact right" will prove the goal.
8. If one wishes to disprove (i.e. derive false from) one of the current hypotheses
   of the form h : x ∈ {a,b,c,d,e}, using "cases h" will replace the current hypothesis
   with a_1 : x ∈ {a,b,c,d}, provided that x and e are not equal. When the set is empty,
   "cases h" will directly prove false.

HINT: To prove a statement of the form "P → P", use "exact id".

RECAP:
1. To prove a statement of the form "∀ z, P z", type "intro x" and then prove "P x".
2. To prove a statement of the form "P → Q", type "intro HP" and then prove "Q".

NOTE:
Somehow the goal will become messy. I tried to fix it but it just wouldn't work.
Try to work with simple cases where the goal still works, and then trust in the
force when it doesn't.

-/

definition A : zfc := {1,2,3,4,5}

-- prove one and delete the other for each part.

theorem M1F_Sheet01_Q05a_is_true : (1:zfc) ∈ A := sorry
theorem M1F_Sheet01_Q05a_is_false : (1:zfc) ∈ A := sorry

theorem M1F_Sheet01_Q05b_is_true: ({1}:zfc) ∈ A := sorry
theorem M1F_Sheet01_Q05b_is_false: ({1}:zfc) ∉ A := sorry

theorem M1F_Sheet01_Q05c_is_true: ({1}:zfc) ⊆ A := sorry
theorem M1F_Sheet01_Q05c_is_false: ({1}:zfc) ⊈ A := sorry

-- The goal generator gets messy in (d) and (e).

theorem M1F_Sheet01_Q05d_is_true: ({1,2}:zfc) ⊆ A := sorry
theorem M1F_Sheet01_Q05d_is_false: ({1,2}:zfc) ⊆ A := sorry

theorem M1F_Sheet01_Q05e_is_true: ({1,2,1}:zfc) ⊆ A := sorry
theorem M1F_Sheet01_Q05e_is_false: ({1,2,1}:zfc) ⊈ A := sorry

theorem M1F_Sheet01_Q05f_is_true: ({1,1}:zfc) ∈ A := sorry
theorem M1F_Sheet01_Q05f_is_false: ({1,1}:zfc) ∉ A := sorry

theorem M1F_Sheet01_Q05g_is_true: A ∈ A := sorry
theorem M1F_Sheet01_Q05g_is_false: A ∉ A := sorry

theorem M1F_Sheet01_Q05h_is_true: A ⊇ A := sorry
theorem M1F_Sheet01_Q05h_is_false: A ⊉ A := sorry


import game.limits.L01defs
import game.limits.seq_limitLinear

open real

namespace xena -- hide

notation `|` x `|` := abs x -- hide

/-
Use previous results to obtain the limit of a product if individual limits
for the factors are both zero.
-/

/- Lemma
If $\lim_{n \to \infty} a_n = 0$ and $\lim_{n \to \infty} b_n = 0$,
then
$\lim_{n \to \infty} ( a_n * b_n) = 0$
-/
lemma lim_zero_prod (a : ℕ → ℝ) (b : ℕ → ℝ)
    (ha : is_limit a 0) (hb : is_limit b 0) :
    is_limit ( λ n, (a n) * (b n) ) 0 :=
begin
    unfold is_limit,
    intros ε hε,
    set sε := sqrt ε with hsε,
    have h1 : 0 < sε, from  real.sqrt_pos.mpr hε,
    have Ha := ha sε h1,
    have Hb := hb sε h1,
    cases Ha with Na hNa,
    cases Hb with Nb hNb,
    set N := max Na Nb with hN,
    use N,
    intros n hn,
    have Ha := hNa n (le_of_max_le_left hn),
    have Hb := hNb n (le_of_max_le_right hn),
    rw sub_zero at *, rw abs_mul,
    have g1 := mul_lt_mul_of_pos_right Hb h1,
    have g2 : sε ^ 2 = ε, exact sqr_sqrt( le_of_lt hε),
    have g3 : sε * sε = sε ^2, ring,
    rw g3 at g1, rw g2 at g1, rw mul_comm at g1,
    have hbn : 0 ≤ |b n|, exact is_absolute_value.abv_nonneg abs (b n),
    have G := mul_le_mul_of_nonneg_right (le_of_lt Ha) hbn,
    exact lt_of_le_of_lt G g1, -- linarith fails!
    done
end

end xena -- hide


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
import lemmas2
noncomputable theory

open nat nat.modeq zmod euclidean_domain lemmas2
namespace defs_lemmas

/- DEFINITIONS -/

/--
A structure for a congruence x ≡ a [MOD n]
-/
def cong := (Σ (n : ℕ), zmod n)

/--
A list of congruence relations
-/
def congruences := list cong

/-Examples of above definitions
 def x : cong := ⟨5, ↑2⟩
 def y : congruences := [⟨5, ↑2⟩ , ⟨3, ↑2⟩]
-/

/- LIST PROPERTIES -/

/--
All moduli for the congruences in the list are pairwise coprime
-/
def pairwise_coprime  (l : congruences) : Prop :=
            list.pairwise (λ (x y : cong), nat.coprime x.1 y.1) l

/--
All moduli for the congruences in the list are nonzero
-/
def nonzero_cong ( l : congruences) : Prop :=
            list.all l (λ (c : cong), 0 < c.1)

/--
Defines when x is a solution to the list of congruences in l
-/
def solution (x : ℕ) (l : congruences) : Prop :=
            list.all l (λ (c : cong), modeq c.1 x c.2.val)

/--
Takes the product of the defining moduli of all congruences in the list
-/
def cong_prod : congruences → ℕ
    | list.nil := 1
    | (h :: t) := h.1 * cong_prod t
--

/- LEMMAS ABOUT LIST PROPERTIES -/

/--
If a list satisfies nonzero_cong, so does the tail and the head has nonzero moduli
-/
lemma subset_nonzero (c : cong) (l : list cong) (H : nonzero_cong (c :: l)) :
                                 0 < c.1 ∧ nonzero_cong l :=
begin
    unfold nonzero_cong at H,
    rw list.all_iff_forall_prop at H,
    split,
    { exact H c (by exact list.mem_cons_self c l), },
    {   unfold nonzero_cong,
        rw list.all_iff_forall_prop,
        rintros a ha,
        exact H a (by exact list.mem_cons_of_mem _ ha), }
end

/--
If a list satisfies pairwise_coprime the head is coprime to all
moduli in the tail and the tail satisfies pairwise_coprime
-/
lemma subset_coprime (c : cong) (l : list cong) (H : pairwise_coprime (c :: l)) :
                (∀ (a : cong), a ∈ l → coprime c.1 a.1) ∧ pairwise_coprime l :=
begin
    unfold pairwise_coprime at H,
    rw list.pairwise_cons at H,
    exact H,
end

/--
The modulus for an element of list of congruences will divide the
cong_prod of the list, the product of all moduli in the list
-/
lemma mem_div_prod (l : list cong) (a : cong) (H : a ∈ l) : a.1 ∣ cong_prod l :=
begin
    induction l with head tail ihtail,
     --nil case
    exfalso,
    exact H,
    --induction case
    dsimp[cong_prod],
    cases H,
    rw H,
    simp only [nat.dvd_mul_right],
    specialize ihtail H,
    cases ihtail with c hc,
    rw hc,
    rw mul_comm,
    use c * head.fst,
    ring,
end

/-  LEMMAS ABOUT CONG_PROD OUTPUTS -/

/--
 Given a list of congruences with nonzero (i.e. positive)
   moduli, the product of those moduli will be positive
-/
lemma pos_prod (l : congruences) (H : nonzero_cong l) : 0 < cong_prod l :=
begin
    induction l with head tail ihtail,
    {   dsimp [cong_prod],
        linarith, },
    {   have nonzero_parts := subset_nonzero head tail H,
        specialize ihtail nonzero_parts.right,
        dsimp [cong_prod],
        exact mul_pos nonzero_parts.left ihtail,}
end

/--
The modulus of the first congruence is coprime to the product of the moduli of
the tail of the list assuming that the entire list satisfies pairwise_coprime
-/
lemma coprime_prod (c : cong) (l : list cong) (H : pairwise_coprime (c :: l)) :
                             coprime c.1 (cong_prod l) :=
begin
    induction l with head tail ihtail,
    {   dsimp[cong_prod],
        by exact c.fst.coprime_one_right, },
    {   dsimp[cong_prod],
        apply nat.coprime.mul_right,
        exact (subset_coprime c (head :: tail) H).left head (by exact list.mem_cons_self head tail),
        apply ihtail,
        unfold pairwise_coprime at *,
        rw list.pairwise_cons at *,
        split,
        intros a ha,
        refine H.left a (by exact list.mem_cons_of_mem head ha),
        exact list.pairwise_of_pairwise_cons H.right, },
end

end defs_lemmas


import MyNat.Definition
namespace MyNat
open MyNat
/-!

# Advanced proposition world.

## Level 2: the `cases` tactic.

If `P ∧ Q` is in the goal, then we can make progress with `constructor`. But what if `P ∧ Q` is a
hypothesis?

The lemma below asks us to prove `P ∧ Q → Q ∧ P`, that is, symmetry of the "and" relation. The
obvious first move is

`intro h`

because the goal is an implication and this tactic is guaranteed to make progress. Now `h : P ∧ Q`
is a hypothesis, and we can extract the parts of this `And.intro` using the [`cases` tactic](../Tactics/cases.lean.md)

`cases h with`

This will give us two hypotheses `p` and `q` proving `P` and `Q` respectively.  So we hold onto
these, the goal is now `⊢ Q ∧ P` which we can split using the `constructor` tactic, then we can
easily pick off the two sub-goals `⊢ Q` and  `⊢ P` using `q` and `p` respectively.

## Lemma
If `P` and `Q` are true/false statements, then `P ∧ Q ⟹ Q ∧ P`.
-/
lemma and_symm (P Q : Prop) : P ∧ Q → Q ∧ P := by
  intro h
  cases h with
  | intro p q =>
    constructor
    exact q
    exact p

/-!

Next up [Level 3](./Level3.lean.md)
-/


/-
Copyright (c) 2020 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joseph Myers, Yury Kudryashov

! This file was ported from Lean 3 source module algebra.add_torsor
! leanprover-community/mathlib commit 9003f28797c0664a49e4179487267c494477d853
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Set.Pointwise.SMul

/-!
# Torsors of additive group actions

This file defines torsors of additive group actions.

## Notations

The group elements are referred to as acting on points.  This file
defines the notation `+ᵥ` for adding a group element to a point and
`-ᵥ` for subtracting two points to produce a group element.

## Implementation notes

Affine spaces are the motivating example of torsors of additive group actions. It may be appropriate
to refactor in terms of the general definition of group actions, via `to_additive`, when there is a
use for multiplicative torsors (currently mathlib only develops the theory of group actions for
multiplicative group actions).

## Notations

* `v +ᵥ p` is a notation for `VAdd.vadd`, the left action of an additive monoid;

* `p₁ -ᵥ p₂` is a notation for `VSub.vsub`, difference between two points in an additive torsor
  as an element of the corresponding additive group;

## References

* https://en.wikipedia.org/wiki/Principal_homogeneous_space
* https://en.wikipedia.org/wiki/Affine_space

-/

/-- An `AddTorsor G P` gives a structure to the nonempty type `P`,
acted on by an `AddGroup G` with a transitive and free action given
by the `+ᵥ` operation and a corresponding subtraction given by the
`-ᵥ` operation. In the case of a vector space, it is an affine
space. -/
class AddTorsor (G : outParam (Type _)) (P : Type _) [outParam <| AddGroup G] extends AddAction G P,
  VSub G P where
  [Nonempty : Nonempty P]
  /-- Torsor subtraction and addition with the same element cancels out. -/
  vsub_vadd' : ∀ p1 p2 : P, (p1 -ᵥ p2 : G) +ᵥ p2 = p1
  /-- Torsor addition and subtraction with the same element cancels out. -/
  vadd_vsub' : ∀ (g : G) (p : P), g +ᵥ p -ᵥ p = g
#align add_torsor AddTorsor

attribute [instance] AddTorsor.Nonempty -- porting note: removers `nolint instance_priority`

--Porting note: removed
--attribute [nolint dangerous_instance] AddTorsor.toVSub

/-- An `AddGroup G` is a torsor for itself. -/
--@[nolint instance_priority] Porting note: linter does not exist
instance addGroupIsAddTorsor (G : Type _) [AddGroup G] : AddTorsor G G
    where
  vsub := Sub.sub
  vsub_vadd' := sub_add_cancel
  vadd_vsub' := add_sub_cancel
#align add_group_is_add_torsor addGroupIsAddTorsor

/-- Simplify subtraction for a torsor for an `AddGroup G` over
itself. -/
@[simp]
theorem vsub_eq_sub {G : Type _} [AddGroup G] (g1 g2 : G) : g1 -ᵥ g2 = g1 - g2 :=
  rfl
#align vsub_eq_sub vsub_eq_sub

section General

variable {G : Type _} {P : Type _} [AddGroup G] [T : AddTorsor G P]

/-- Adding the result of subtracting from another point produces that
point. -/
@[simp]
theorem vsub_vadd (p1 p2 : P) : p1 -ᵥ p2 +ᵥ p2 = p1 :=
  AddTorsor.vsub_vadd' p1 p2
#align vsub_vadd vsub_vadd

/-- Adding a group element then subtracting the original point
produces that group element. -/
@[simp]
theorem vadd_vsub (g : G) (p : P) : g +ᵥ p -ᵥ p = g :=
  AddTorsor.vadd_vsub' g p
#align vadd_vsub vadd_vsub

/-- If the same point added to two group elements produces equal
results, those group elements are equal. -/
theorem vadd_right_cancel {g1 g2 : G} (p : P) (h : g1 +ᵥ p = g2 +ᵥ p) : g1 = g2 := by
-- Porting note: vadd_vsub g₁ → vadd_vsub g₁ p
  rw [← vadd_vsub g1 p, h, vadd_vsub]
#align vadd_right_cancel vadd_right_cancel

@[simp]
theorem vadd_right_cancel_iff {g1 g2 : G} (p : P) : g1 +ᵥ p = g2 +ᵥ p ↔ g1 = g2 :=
  ⟨vadd_right_cancel p, fun h => h ▸ rfl⟩
#align vadd_right_cancel_iff vadd_right_cancel_iff

/-- Adding a group element to the point `p` is an injective
function. -/
theorem vadd_right_injective (p : P) : Function.Injective ((· +ᵥ p) : G → P) := fun _ _ =>
  vadd_right_cancel p
#align vadd_right_injective vadd_right_injective

/-- Adding a group element to a point, then subtracting another point,
produces the same result as subtracting the points then adding the
group element. -/
theorem vadd_vsub_assoc (g : G) (p1 p2 : P) : g +ᵥ p1 -ᵥ p2 = g + (p1 -ᵥ p2) :=
  by
  apply vadd_right_cancel p2
  rw [vsub_vadd, add_vadd, vsub_vadd]
#align vadd_vsub_assoc vadd_vsub_assoc

/-- Subtracting a point from itself produces 0. -/
@[simp]
theorem vsub_self (p : P) : p -ᵥ p = (0 : G) := by
  rw [← zero_add (p -ᵥ p), ← vadd_vsub_assoc, vadd_vsub]
#align vsub_self vsub_self

/-- If subtracting two points produces 0, they are equal. -/
theorem eq_of_vsub_eq_zero {p1 p2 : P} (h : p1 -ᵥ p2 = (0 : G)) : p1 = p2 := by
  rw [← vsub_vadd p1 p2, h, zero_vadd]
#align eq_of_vsub_eq_zero eq_of_vsub_eq_zero

/-- Subtracting two points produces 0 if and only if they are
equal. -/
@[simp]
theorem vsub_eq_zero_iff_eq {p1 p2 : P} : p1 -ᵥ p2 = (0 : G) ↔ p1 = p2 :=
  Iff.intro eq_of_vsub_eq_zero fun h => h ▸ vsub_self _
#align vsub_eq_zero_iff_eq vsub_eq_zero_iff_eq

theorem vsub_ne_zero {p q : P} : p -ᵥ q ≠ (0 : G) ↔ p ≠ q :=
  not_congr vsub_eq_zero_iff_eq
#align vsub_ne_zero vsub_ne_zero

/-- Cancellation adding the results of two subtractions. -/
@[simp]
theorem vsub_add_vsub_cancel (p1 p2 p3 : P) : p1 -ᵥ p2 + (p2 -ᵥ p3) = p1 -ᵥ p3 :=
  by
  apply vadd_right_cancel p3
  rw [add_vadd, vsub_vadd, vsub_vadd, vsub_vadd]
#align vsub_add_vsub_cancel vsub_add_vsub_cancel

/-- Subtracting two points in the reverse order produces the negation
of subtracting them. -/
@[simp]
theorem neg_vsub_eq_vsub_rev (p1 p2 : P) : -(p1 -ᵥ p2) = p2 -ᵥ p1 :=
  by
  refine' neg_eq_of_add_eq_zero_right (vadd_right_cancel p1 _)
  rw [vsub_add_vsub_cancel, vsub_self]
#align neg_vsub_eq_vsub_rev neg_vsub_eq_vsub_rev

theorem vadd_vsub_eq_sub_vsub (g : G) (p q : P) : g +ᵥ p -ᵥ q = g - (q -ᵥ p) := by
  rw [vadd_vsub_assoc, sub_eq_add_neg, neg_vsub_eq_vsub_rev]
#align vadd_vsub_eq_sub_vsub vadd_vsub_eq_sub_vsub

/-- Subtracting the result of adding a group element produces the same result
as subtracting the points and subtracting that group element. -/
theorem vsub_vadd_eq_vsub_sub (p1 p2 : P) (g : G) : p1 -ᵥ (g +ᵥ p2) = p1 -ᵥ p2 - g := by
  rw [← add_right_inj (p2 -ᵥ p1 : G), vsub_add_vsub_cancel, ← neg_vsub_eq_vsub_rev, vadd_vsub, ←
    add_sub_assoc, ← neg_vsub_eq_vsub_rev, neg_add_self, zero_sub]
#align vsub_vadd_eq_vsub_sub vsub_vadd_eq_vsub_sub

/-- Cancellation subtracting the results of two subtractions. -/
@[simp]
theorem vsub_sub_vsub_cancel_right (p1 p2 p3 : P) : p1 -ᵥ p3 - (p2 -ᵥ p3) = p1 -ᵥ p2 := by
  rw [← vsub_vadd_eq_vsub_sub, vsub_vadd]
#align vsub_sub_vsub_cancel_right vsub_sub_vsub_cancel_right

/-- Convert between an equality with adding a group element to a point
and an equality of a subtraction of two points with a group
element. -/
theorem eq_vadd_iff_vsub_eq (p1 : P) (g : G) (p2 : P) : p1 = g +ᵥ p2 ↔ p1 -ᵥ p2 = g :=
  ⟨fun h => h.symm ▸ vadd_vsub _ _, fun h => h ▸ (vsub_vadd _ _).symm⟩
#align eq_vadd_iff_vsub_eq eq_vadd_iff_vsub_eq

theorem vadd_eq_vadd_iff_neg_add_eq_vsub {v₁ v₂ : G} {p₁ p₂ : P} :
    v₁ +ᵥ p₁ = v₂ +ᵥ p₂ ↔ -v₁ + v₂ = p₁ -ᵥ p₂ := by
  rw [eq_vadd_iff_vsub_eq, vadd_vsub_assoc, ← add_right_inj (-v₁), neg_add_cancel_left, eq_comm]
#align vadd_eq_vadd_iff_neg_add_eq_vsub vadd_eq_vadd_iff_neg_add_eq_vsub

namespace Set

open Pointwise

-- Porting note: simp can prove this
--@[simp]
theorem singleton_vsub_self (p : P) : ({p} : Set P) -ᵥ {p} = {(0 : G)} := by
  rw [Set.singleton_vsub_singleton, vsub_self]
#align set.singleton_vsub_self Set.singleton_vsub_self

end Set

@[simp]
theorem vadd_vsub_vadd_cancel_right (v₁ v₂ : G) (p : P) : v₁ +ᵥ p -ᵥ (v₂ +ᵥ p) = v₁ - v₂ := by
  rw [vsub_vadd_eq_vsub_sub, vadd_vsub_assoc, vsub_self, add_zero]
#align vadd_vsub_vadd_cancel_right vadd_vsub_vadd_cancel_right

/-- If the same point subtracted from two points produces equal
results, those points are equal. -/
theorem vsub_left_cancel {p1 p2 p : P} (h : p1 -ᵥ p = p2 -ᵥ p) : p1 = p2 := by
  rwa [← sub_eq_zero, vsub_sub_vsub_cancel_right, vsub_eq_zero_iff_eq] at h
#align vsub_left_cancel vsub_left_cancel

/-- The same point subtracted from two points produces equal results
if and only if those points are equal. -/
@[simp]
theorem vsub_left_cancel_iff {p1 p2 p : P} : p1 -ᵥ p = p2 -ᵥ p ↔ p1 = p2 :=
  ⟨vsub_left_cancel, fun h => h ▸ rfl⟩
#align vsub_left_cancel_iff vsub_left_cancel_iff

/-- Subtracting the point `p` is an injective function. -/
theorem vsub_left_injective (p : P) : Function.Injective ((· -ᵥ p) : P → G) := fun _ _ =>
  vsub_left_cancel
#align vsub_left_injective vsub_left_injective

/-- If subtracting two points from the same point produces equal
results, those points are equal. -/
theorem vsub_right_cancel {p1 p2 p : P} (h : p -ᵥ p1 = p -ᵥ p2) : p1 = p2 :=
  by
  refine' vadd_left_cancel (p -ᵥ p2) _
  rw [vsub_vadd, ← h, vsub_vadd]
#align vsub_right_cancel vsub_right_cancel

/-- Subtracting two points from the same point produces equal results
if and only if those points are equal. -/
@[simp]
theorem vsub_right_cancel_iff {p1 p2 p : P} : p -ᵥ p1 = p -ᵥ p2 ↔ p1 = p2 :=
  ⟨vsub_right_cancel, fun h => h ▸ rfl⟩
#align vsub_right_cancel_iff vsub_right_cancel_iff

/-- Subtracting a point from the point `p` is an injective
function. -/
theorem vsub_right_injective (p : P) : Function.Injective ((p -ᵥ ·) : P → G) := fun _ _ =>
  vsub_right_cancel
#align vsub_right_injective vsub_right_injective

end General

section comm

variable {G : Type _} {P : Type _} [AddCommGroup G] [AddTorsor G P]

-- Porting note: Removed:
-- include G

/-- Cancellation subtracting the results of two subtractions. -/
@[simp]
theorem vsub_sub_vsub_cancel_left (p1 p2 p3 : P) : p3 -ᵥ p2 - (p3 -ᵥ p1) = p1 -ᵥ p2 := by
  rw [sub_eq_add_neg, neg_vsub_eq_vsub_rev, add_comm, vsub_add_vsub_cancel]
#align vsub_sub_vsub_cancel_left vsub_sub_vsub_cancel_left

@[simp]
theorem vadd_vsub_vadd_cancel_left (v : G) (p1 p2 : P) : v +ᵥ p1 -ᵥ (v +ᵥ p2) = p1 -ᵥ p2 := by
  rw [vsub_vadd_eq_vsub_sub, vadd_vsub_assoc, add_sub_cancel']
#align vadd_vsub_vadd_cancel_left vadd_vsub_vadd_cancel_left

theorem vsub_vadd_comm (p1 p2 p3 : P) : (p1 -ᵥ p2 : G) +ᵥ p3 = p3 -ᵥ p2 +ᵥ p1 := by
  rw [← @vsub_eq_zero_iff_eq G, vadd_vsub_assoc, vsub_vadd_eq_vsub_sub]
  simp
#align vsub_vadd_comm vsub_vadd_comm

theorem vadd_eq_vadd_iff_sub_eq_vsub {v₁ v₂ : G} {p₁ p₂ : P} :
    v₁ +ᵥ p₁ = v₂ +ᵥ p₂ ↔ v₂ - v₁ = p₁ -ᵥ p₂ := by
  rw [vadd_eq_vadd_iff_neg_add_eq_vsub, neg_add_eq_sub]
#align vadd_eq_vadd_iff_sub_eq_vsub vadd_eq_vadd_iff_sub_eq_vsub

theorem vsub_sub_vsub_comm (p₁ p₂ p₃ p₄ : P) : p₁ -ᵥ p₂ - (p₃ -ᵥ p₄) = p₁ -ᵥ p₃ - (p₂ -ᵥ p₄) := by
  rw [← vsub_vadd_eq_vsub_sub, vsub_vadd_comm, vsub_vadd_eq_vsub_sub]
#align vsub_sub_vsub_comm vsub_sub_vsub_comm

end comm

namespace Prod

variable {G : Type _} {P : Type _} {G' : Type _} {P' : Type _} [AddGroup G] [AddGroup G']
  [AddTorsor G P] [AddTorsor G' P']

-- Porting note: the `{_ : ...}` instance terms make this instance not dangerous
instance {G : Type _} {P : Type _} {G' : Type _} {P' : Type _} {_ : AddGroup G} {_ : AddGroup G'}
    [AddTorsor G P] [AddTorsor G' P'] : AddTorsor (G × G') (P × P') where
  vadd v p := (v.1 +ᵥ p.1, v.2 +ᵥ p.2)
  zero_vadd _ := Prod.ext (zero_vadd _ _) (zero_vadd _ _)
  add_vadd _ _ _ := Prod.ext (add_vadd _ _ _) (add_vadd _ _ _)
  vsub p₁ p₂ := (p₁.1 -ᵥ p₂.1, p₁.2 -ᵥ p₂.2)
  Nonempty := Prod.Nonempty
  vsub_vadd' _ _ := Prod.ext (vsub_vadd _ _) (vsub_vadd _ _)
  vadd_vsub' _ _ := Prod.ext (vadd_vsub _ _) (vadd_vsub _ _)

-- Porting note: The proofs above used to be shorter:
-- zero_vadd p := by simp ⊢ 0 +ᵥ p = p
-- add_vadd := by simp [add_vadd] ⊢ ∀ (a : G) (b : G') (a_1 : G) (b_1 : G') (a_2 : P) (b_2 : P'),
--  (a + a_1, b + b_1) +ᵥ (a_2, b_2) = (a, b) +ᵥ ((a_1, b_1) +ᵥ (a_2, b_2))
-- vsub_vadd' p₁ p₂ := show (p₁.1 -ᵥ p₂.1 +ᵥ p₂.1, _) = p₁ by simp
--   ⊢ (p₁.fst -ᵥ p₂.fst +ᵥ p₂.fst, ((p₁.fst -ᵥ p₂.fst, p₁.snd -ᵥ p₂.snd) +ᵥ p₂).snd) = p₁
-- vadd_vsub' v p := show (v.1 +ᵥ p.1 -ᵥ p.1, v.2 +ᵥ p.2 -ᵥ p.2) = v by simp
--   ⊢ (v.fst +ᵥ p.fst -ᵥ p.fst, v.snd) = v

@[simp]
theorem fst_vadd (v : G × G') (p : P × P') : (v +ᵥ p).1 = v.1 +ᵥ p.1 :=
  rfl
#align prod.fst_vadd Prod.fst_vadd

@[simp]
theorem snd_vadd (v : G × G') (p : P × P') : (v +ᵥ p).2 = v.2 +ᵥ p.2 :=
  rfl
#align prod.snd_vadd Prod.snd_vadd

@[simp]
theorem mk_vadd_mk (v : G) (v' : G') (p : P) (p' : P') : (v, v') +ᵥ (p, p') = (v +ᵥ p, v' +ᵥ p') :=
  rfl
#align prod.mk_vadd_mk Prod.mk_vadd_mk

@[simp]
theorem fst_vsub (p₁ p₂ : P × P') : (p₁ -ᵥ p₂ : G × G').1 = p₁.1 -ᵥ p₂.1 :=
  rfl
#align prod.fst_vsub Prod.fst_vsub

@[simp]
theorem snd_vsub (p₁ p₂ : P × P') : (p₁ -ᵥ p₂ : G × G').2 = p₁.2 -ᵥ p₂.2 :=
  rfl
#align prod.snd_vsub Prod.snd_vsub

@[simp]
theorem mk_vsub_mk (p₁ p₂ : P) (p₁' p₂' : P') :
    ((p₁, p₁') -ᵥ (p₂, p₂') : G × G') = (p₁ -ᵥ p₂, p₁' -ᵥ p₂') :=
  rfl
#align prod.mk_vsub_mk Prod.mk_vsub_mk

end Prod

namespace Pi

universe u v w

variable {I : Type u} {fg : I → Type v} [∀ i, AddGroup (fg i)] {fp : I → Type w}

open AddAction AddTorsor

/-- A product of `AddTorsor`s is an `AddTorsor`. -/
instance [T : ∀ i, AddTorsor (fg i) (fp i)] : AddTorsor (∀ i, fg i) (∀ i, fp i) where
  vadd g p i := g i +ᵥ p i
  zero_vadd p := funext fun i => zero_vadd (fg i) (p i)
  add_vadd g₁ g₂ p := funext fun i => add_vadd (g₁ i) (g₂ i) (p i)
  vsub p₁ p₂ i := p₁ i -ᵥ p₂ i
  Nonempty := ⟨fun i => Classical.choice (T i).Nonempty⟩
  vsub_vadd' p₁ p₂ := funext fun i => vsub_vadd (p₁ i) (p₂ i)
  vadd_vsub' g p := funext fun i => vadd_vsub (g i) (p i)

end Pi

namespace Equiv

variable {G : Type _} {P : Type _} [AddGroup G] [AddTorsor G P]

-- Porting note: Removed:
-- include G

/-- `v ↦ v +ᵥ p` as an equivalence. -/
def vaddConst (p : P) : G ≃ P where
  toFun v := v +ᵥ p
  invFun p' := p' -ᵥ p
  left_inv _ := vadd_vsub _ _
  right_inv _ := vsub_vadd _ _
#align equiv.vadd_const Equiv.vaddConst

@[simp]
theorem coe_vaddConst (p : P) : ⇑(vaddConst p) = fun v => v +ᵥ p :=
  rfl
#align equiv.coe_vadd_const Equiv.coe_vaddConst

@[simp]

/-- `p' ↦ p -ᵥ p'` as an equivalence. -/
def constVSub (p : P) : P ≃ G where
  toFun := (· -ᵥ ·) p
  invFun v := -v +ᵥ p
  left_inv p' := by simp
  right_inv v := by simp [vsub_vadd_eq_vsub_sub]
#align equiv.const_vsub Equiv.constVSub

@[simp]
theorem coe_constVSub (p : P) : ⇑(constVSub p) = (· -ᵥ ·) p :=
  rfl
#align equiv.coe_const_vsub Equiv.coe_constVSub

@[simp]
theorem coe_constVSub_symm (p : P) : ⇑(constVSub p).symm = fun (v : G) => -v +ᵥ p :=
  rfl
#align equiv.coe_const_vsub_symm Equiv.coe_constVSub_symm

variable (P)

/-- The permutation given by `p ↦ v +ᵥ p`. -/
def constVAdd (v : G) : Equiv.Perm P where
  toFun := (· +ᵥ ·) v
  invFun := (· +ᵥ ·) (-v)
  left_inv p := by simp [vadd_vadd]
  right_inv p := by simp [vadd_vadd]
#align equiv.const_vadd Equiv.constVAdd

@[simp]
theorem coe_constVAdd (v : G) : ⇑(constVAdd P v) = (· +ᵥ ·) v :=
  rfl
#align equiv.coe_const_vadd Equiv.coe_constVAdd

variable (G)

@[simp]
theorem constVAdd_zero : constVAdd P (0 : G) = 1 :=
  ext <| zero_vadd G
#align equiv.const_vadd_zero Equiv.constVAdd_zero

variable {G}

@[simp]
theorem constVAdd_add (v₁ v₂ : G) : constVAdd P (v₁ + v₂) = constVAdd P v₁ * constVAdd P v₂ :=
  ext <| add_vadd v₁ v₂
#align equiv.const_vadd_add Equiv.constVAdd_add

/-- `Equiv.constVAdd` as a homomorphism from `Multiplicative G` to `Equiv.perm P` -/
def constVAddHom : Multiplicative G →* Equiv.Perm P where
  toFun v := constVAdd P (Multiplicative.toAdd v)
  map_one' := constVAdd_zero G P
  map_mul' := constVAdd_add P
#align equiv.const_vadd_hom Equiv.constVAddHom

variable {P}

-- Porting note: Previous code was:
-- open _Root_.Function
open Function

/-- Point reflection in `x` as a permutation. -/
def pointReflection (x : P) : Perm P :=
  (constVSub x).trans (vaddConst x)
#align equiv.point_reflection Equiv.pointReflection

theorem pointReflection_apply (x y : P) : pointReflection x y = x -ᵥ y +ᵥ x :=
  rfl
#align equiv.point_reflection_apply Equiv.pointReflection_apply

@[simp]
theorem pointReflection_symm (x : P) : (pointReflection x).symm = pointReflection x :=
  ext <| by simp [pointReflection]
#align equiv.point_reflection_symm Equiv.pointReflection_symm

@[simp]
theorem pointReflection_self (x : P) : pointReflection x x = x :=
  vsub_vadd _ _
#align equiv.point_reflection_self Equiv.pointReflection_self

theorem pointReflection_involutive (x : P) : Involutive (pointReflection x : P → P) := fun y =>
  (Equiv.apply_eq_iff_eq_symm_apply _).2 <| by rw [pointReflection_symm]
#align equiv.point_reflection_involutive Equiv.pointReflection_involutive

set_option linter.deprecated false
/-- `x` is the only fixed point of `pointReflection x`. This lemma requires
`x + x = y + y ↔ x = y`. There is no typeclass to use here, so we add it as an explicit argument. -/
theorem pointReflection_fixed_iff_of_injective_bit0 {x y : P} (h : Injective (bit0 : G → G)) :
    pointReflection x y = y ↔ y = x := by
  rw [pointReflection_apply, eq_comm, eq_vadd_iff_vsub_eq, ← neg_vsub_eq_vsub_rev,
    neg_eq_iff_add_eq_zero, ← bit0, ← bit0_zero, h.eq_iff, vsub_eq_zero_iff_eq, eq_comm]
#align
  equiv.point_reflection_fixed_iff_of_injective_bit0
  Equiv.pointReflection_fixed_iff_of_injective_bit0

-- Porting note: Removed:
-- omit G

-- Porting note: need this to calm down CI
theorem injective_pointReflection_left_of_injective_bit0 {G P : Type _} [AddCommGroup G]
    [AddTorsor G P] (h : Injective (bit0 : G → G)) (y : P) :
    Injective fun x : P => pointReflection x y :=
  fun x₁ x₂ (hy : pointReflection x₁ y = pointReflection x₂ y) => by
  rwa [pointReflection_apply, pointReflection_apply, vadd_eq_vadd_iff_sub_eq_vsub,
    vsub_sub_vsub_cancel_right, ← neg_vsub_eq_vsub_rev, neg_eq_iff_add_eq_zero, ← bit0, ← bit0_zero,
    h.eq_iff, vsub_eq_zero_iff_eq] at hy
#align
  equiv.injective_point_reflection_left_of_injective_bit0
  Equiv.injective_pointReflection_left_of_injective_bit0

end Equiv

theorem AddTorsor.subsingleton_iff (G P : Type _) [AddGroup G] [AddTorsor G P] :
    Subsingleton G ↔ Subsingleton P := by
  inhabit P
  exact (Equiv.vaddConst default).subsingleton_congr
#align add_torsor.subsingleton_iff AddTorsor.subsingleton_iff


/-
Copyright (c) 2021 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen
-/
import algebra.algebra.basic
import number_theory.class_number.admissible_absolute_value

/-!
# Admissible absolute value on the integers
This file defines an admissible absolute value `absolute_value.abs_is_admissible`
which we use to show the class number of the ring of integers of a number field
is finite.

## Main results

 * `absolute_value.abs_is_admissible` shows the "standard" absolute value on `ℤ`,
   mapping negative `x` to `-x`, is admissible.
-/

namespace absolute_value

open int

/-- We can partition a finite family into `partition_card ε` sets, such that the remainders
in each set are close together. -/
lemma exists_partition_int (n : ℕ) {ε : ℝ} (hε : 0 < ε) {b : ℤ} (hb : b ≠ 0) (A : fin n → ℤ) :
  ∃ (t : fin n → fin ⌈1 / ε⌉₊),
  ∀ i₀ i₁, t i₀ = t i₁ → ↑(abs (A i₁ % b - A i₀ % b)) < abs b • ε :=
begin
  have hb' : (0 : ℝ) < ↑(abs b) := int.cast_pos.mpr (abs_pos.mpr hb),
  have hbε : 0 < abs b • ε,
  { rw algebra.smul_def,
    exact mul_pos hb' hε },
  have hfloor : ∀ i, 0 ≤ floor ((A i % b : ℤ) / (abs b • ε) : ℝ),
  { intro i,
    exact floor_nonneg.mpr (div_nonneg (cast_nonneg.mpr (mod_nonneg _ hb)) hbε.le) },
  refine ⟨λ i, ⟨nat_abs (floor ((A i % b : ℤ) / (abs b • ε) : ℝ)), _⟩, _⟩,
  { rw [← coe_nat_lt, nat_abs_of_nonneg (hfloor i), floor_lt],
    apply lt_of_lt_of_le _ (nat.le_ceil _),
    rw [algebra.smul_def, ring_hom.eq_int_cast, ← div_div_eq_div_mul, div_lt_div_right hε,
        div_lt_iff hb', one_mul, cast_lt],
    exact int.mod_lt _ hb },
  intros i₀ i₁ hi,
  have hi : (⌊↑(A i₀ % b) / abs b • ε⌋.nat_abs : ℤ) = ⌊↑(A i₁ % b) / abs b • ε⌋.nat_abs :=
    congr_arg (coe : ℕ → ℤ) (subtype.mk_eq_mk.mp hi),
  rw [nat_abs_of_nonneg (hfloor i₀), nat_abs_of_nonneg (hfloor i₁)] at hi,
  have hi := abs_sub_lt_one_of_floor_eq_floor hi,
  rw [abs_sub_comm, ← sub_div, abs_div, abs_of_nonneg hbε.le, div_lt_iff hbε, one_mul] at hi,
  rwa [int.cast_abs, int.cast_sub]
end

/-- `abs : ℤ → ℤ` is an admissible absolute value -/
noncomputable def abs_is_admissible : is_admissible absolute_value.abs :=
{ card := λ ε, ⌈1 / ε⌉₊,
  exists_partition' := λ n ε hε b hb, exists_partition_int n hε hb,
  .. absolute_value.abs_is_euclidean }

noncomputable instance : inhabited (is_admissible absolute_value.abs) :=
⟨abs_is_admissible⟩

end absolute_value


/-
Copyright (c) 2019 Neil Strickland. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Neil Strickland, Yury Kudryashov
-/
import algebra.group.semiconj

/-!
# Commuting pairs of elements in monoids

We define the predicate `commute a b := a * b = b * a` and provide some operations on terms `(h :
commute a b)`. E.g., if `a`, `b`, and c are elements of a semiring, and that `hb : commute a b` and
`hc : commute a c`.  Then `hb.pow_left 5` proves `commute (a ^ 5) b` and `(hb.pow_right 2).add_right
(hb.mul_right hc)` proves `commute a (b ^ 2 + b * c)`.

Lean does not immediately recognise these terms as equations, so for rewriting we need syntax like
`rw [(hb.pow_left 5).eq]` rather than just `rw [hb.pow_left 5]`.

This file defines only a few operations (`mul_left`, `inv_right`, etc).  Other operations
(`pow_right`, field inverse etc) are in the files that define corresponding notions.

## Implementation details

Most of the proofs come from the properties of `semiconj_by`.
-/

/-- Two elements commute if `a * b = b * a`. -/
@[to_additive add_commute "Two elements additively commute if `a + b = b + a`"]
def commute {S : Type*} [has_mul S] (a b : S) : Prop := semiconj_by a b b

namespace commute

section has_mul

variables {S : Type*} [has_mul S]

/-- Equality behind `commute a b`; useful for rewriting. -/
@[to_additive "Equality behind `add_commute a b`; useful for rewriting."]
protected lemma eq {a b : S} (h : commute a b) : a * b = b * a := h

/-- Any element commutes with itself. -/
@[refl, simp, to_additive "Any element commutes with itself."]
protected lemma refl (a : S) : commute a a := eq.refl (a * a)

/-- If `a` commutes with `b`, then `b` commutes with `a`. -/
@[symm, to_additive "If `a` commutes with `b`, then `b` commutes with `a`."]
protected lemma symm {a b : S} (h : commute a b) : commute b a := eq.symm h

@[to_additive] protected theorem semiconj_by {a b : S} (h : commute a b) : semiconj_by a b b := h

@[to_additive]
protected theorem symm_iff {a b : S} : commute a b ↔ commute b a :=
⟨commute.symm, commute.symm⟩

end has_mul

section semigroup

variables {S : Type*} [semigroup S] {a b c : S}

/-- If `a` commutes with both `b` and `c`, then it commutes with their product. -/
@[simp, to_additive "If `a` commutes with both `b` and `c`, then it commutes with their sum."]
lemma mul_right (hab : commute a b) (hac : commute a c) : commute a (b * c) := hab.mul_right hac

/-- If both `a` and `b` commute with `c`, then their product commutes with `c`. -/
@[simp, to_additive "If both `a` and `b` commute with `c`, then their product commutes with `c`."]
lemma mul_left (hac : commute a c) (hbc : commute b c) : commute (a * b) c := hac.mul_left hbc

@[to_additive] protected lemma right_comm (h : commute b c) (a : S) :
  a * b * c = a * c * b :=
by simp only [mul_assoc, h.eq]

@[to_additive] protected lemma left_comm (h : commute a b) (c) :
  a * (b * c) = b * (a * c) :=
by simp only [← mul_assoc, h.eq]

end semigroup

@[to_additive]
protected

section mul_one_class

variables {M : Type*} [mul_one_class M]

@[simp, to_additive] theorem one_right (a : M) : commute a 1 := semiconj_by.one_right a
@[simp, to_additive] theorem one_left (a : M) : commute 1 a := semiconj_by.one_left a

end mul_one_class

section monoid

variables {M : Type*} [monoid M] {a b : M} {u u₁ u₂ : Mˣ}

@[simp, to_additive]
theorem pow_right (h : commute a b) (n : ℕ) : commute a (b ^ n) := h.pow_right n
@[simp, to_additive]
theorem pow_left (h : commute a b) (n : ℕ) : commute (a ^ n) b := (h.symm.pow_right n).symm
@[simp, to_additive]
theorem pow_pow (h : commute a b) (m n : ℕ) : commute (a ^ m) (b ^ n) :=
(h.pow_left m).pow_right n

@[simp, to_additive]
theorem self_pow (a : M) (n : ℕ) : commute a (a ^ n) := (commute.refl a).pow_right n
@[simp, to_additive]
theorem pow_self (a : M) (n : ℕ) : commute (a ^ n) a := (commute.refl a).pow_left n
@[simp, to_additive]
theorem pow_pow_self (a : M) (m n : ℕ) : commute (a ^ m) (a ^ n) :=
(commute.refl a).pow_pow m n

@[to_additive succ_nsmul'] theorem _root_.pow_succ' (a : M) (n : ℕ) : a ^ (n + 1) = a ^ n * a :=
(pow_succ a n).trans (self_pow _ _)

@[to_additive] theorem units_inv_right : commute a u → commute a ↑u⁻¹ :=
semiconj_by.units_inv_right

@[simp, to_additive] theorem units_inv_right_iff :
  commute a ↑u⁻¹ ↔ commute a u :=
semiconj_by.units_inv_right_iff

@[to_additive] theorem units_inv_left : commute ↑u a → commute ↑u⁻¹ a :=
semiconj_by.units_inv_symm_left

@[simp, to_additive]
theorem units_inv_left_iff: commute ↑u⁻¹ a ↔ commute ↑u a :=
semiconj_by.units_inv_symm_left_iff

@[to_additive]
theorem units_coe : commute u₁ u₂ → commute (u₁ : M) u₂ := semiconj_by.units_coe
@[to_additive]
theorem units_of_coe : commute (u₁ : M) u₂ → commute u₁ u₂ := semiconj_by.units_of_coe
@[simp, to_additive]
theorem units_coe_iff : commute (u₁ : M) u₂ ↔ commute u₁ u₂ := semiconj_by.units_coe_iff

@[to_additive] lemma is_unit_mul_iff (h : commute a b) :
  is_unit (a * b) ↔ is_unit a ∧ is_unit b :=
begin
  refine ⟨_, λ H, H.1.mul H.2⟩,
  rintro ⟨u, hu⟩,
  have : b * ↑u⁻¹ * a = 1,
  { have : commute a u := hu.symm ▸ (commute.refl _).mul_right h,
    rw [← this.units_inv_right.right_comm, ← h.eq, ← hu, u.mul_inv] },
  split,
  { refine ⟨⟨a, b * ↑u⁻¹, _, this⟩, rfl⟩,
    rw [← mul_assoc, ← hu, u.mul_inv] },
  { rw mul_assoc at this,
    refine ⟨⟨b, ↑u⁻¹ * a, this, _⟩, rfl⟩,
    rw [mul_assoc, ← hu, u.inv_mul] }
end

@[simp, to_additive] lemma _root_.is_unit_mul_self_iff :
  is_unit (a * a) ↔ is_unit a :=
(commute.refl a).is_unit_mul_iff.trans (and_self _)

end monoid

section group

variables {G : Type*} [group G] {a b : G}

@[to_additive]
theorem inv_right : commute a b → commute a b⁻¹ := semiconj_by.inv_right
@[simp, to_additive]
theorem inv_right_iff : commute a b⁻¹ ↔ commute a b := semiconj_by.inv_right_iff

@[to_additive] theorem inv_left :  commute a b → commute a⁻¹ b := semiconj_by.inv_symm_left
@[simp, to_additive]
theorem inv_left_iff : commute a⁻¹ b ↔ commute a b := semiconj_by.inv_symm_left_iff

@[to_additive]
theorem inv_inv : commute a b → commute a⁻¹ b⁻¹ := semiconj_by.inv_inv_symm
@[simp, to_additive]
theorem inv_inv_iff : commute a⁻¹ b⁻¹ ↔ commute a b := semiconj_by.inv_inv_symm_iff

@[to_additive]
protected theorem inv_mul_cancel (h : commute a b) : a⁻¹ * b * a = b :=
by rw [h.inv_left.eq, inv_mul_cancel_right]

@[to_additive]
theorem inv_mul_cancel_assoc (h : commute a b) : a⁻¹ * (b * a) = b :=
by rw [← mul_assoc, h.inv_mul_cancel]

@[to_additive]
protected theorem mul_inv_cancel (h : commute a b) : a * b * a⁻¹ = b :=
by rw [h.eq, mul_inv_cancel_right]

@[to_additive]
theorem mul_inv_cancel_assoc (h : commute a b) : a * (b * a⁻¹) = b :=
by rw [← mul_assoc, h.mul_inv_cancel]

end group

end commute

section comm_group

variables {G : Type*} [comm_group G] (a b : G)

@[simp, to_additive] lemma mul_inv_cancel_comm : a * b * a⁻¹ = b :=
(commute.all a b).mul_inv_cancel

@[simp, to_additive] lemma mul_inv_cancel_comm_assoc : a * (b * a⁻¹) = b :=
(commute.all a b).mul_inv_cancel_assoc

@[simp, to_additive] lemma inv_mul_cancel_comm : a⁻¹ * b * a = b :=
(commute.all a b).inv_mul_cancel

@[simp, to_additive] lemma inv_mul_cancel_comm_assoc : a⁻¹ * (b * a) = b :=
(commute.all a b).inv_mul_cancel_assoc

end comm_group