open Nat

namespace Induction

example : 3 + (4 + 5) = (3 + 4) + 5 :=
  calc
    3 + (4 + 5)
      =  3 + 9        := rfl
    _ =  12           := rfl
    _ =  7 + 5        := rfl
    _ =  (3 + 4) + 5  := rfl

theorem add_zero (m : Nat)   : m + zero     = m            := rfl
theorem add_succ (m n : Nat) : m + (succ n) = succ (m + n) := rfl

theorem add_assoc : ∀ (m n p : Nat), m + (n + p) = (m + n) + p
  | m , n , zero =>
    calc
      m + (n + zero)
        = m + n               := by rw [add_zero n]
      _ = (m + n) + zero      := by rw [add_zero (m + n)]
  | m , n , succ p =>
    calc
      m + (n + succ p)
        = m + succ (n + p)    := by rw [add_succ n p]
      _ = succ (m + (n + p))  := by rw [add_succ m (n + p)]
      _ = succ ((m + n) + p)  := by rw [add_assoc m n p]
      _ = (m + n) + succ p    := by rw [add_succ (m + n) p]

theorem add_assoc' : ∀ (m n p : Nat), m + (n + p) = (m + n) + p
  := by
      intros m n p
      induction p with
      | zero =>
          calc
            m + (n + zero)
              = m + n               := by rfl
            _ = (m + n) + zero      := by rfl
      | succ p ih =>
          calc
            m + (n + succ p)
              = m + succ (n + p)    := by rfl
            _ = succ (m + (n + p))  := by rfl
            _ = succ ((m + n) + p)  := by rw [ih]
            _ = (m + n) + succ p    := by rfl

theorem zero_add : ∀ (n : Nat), zero + n = n
  | zero =>
    calc
      zero + zero
        = zero              := rfl
  | succ n =>
    calc
      zero + succ n
        =  succ (zero + n)  := rfl
      _ =  succ n           := by rw [zero_add n]

theorem succ_add : ∀ (m n : Nat), (succ m) + n = succ (m + n)
  | m , zero =>
    calc
      (succ m) + zero
        = succ m            := rfl
      _ = succ (m + zero)   := rfl
  | m , succ n =>
    calc
      (succ m) + (succ n)
        = succ ((succ m) + n)  := rfl
      _ = succ (succ (m + n))  := by rw [succ_add m n]
      _ = succ (m + (succ n))  := rfl

theorem comm_add : ∀ (m n : Nat), m + n = n + m
  | zero , n =>
    calc
      zero + n
        = n                     := by rw [zero_add n]
      _ = n + zero              := rfl
  | succ m , n =>
    calc
      (succ m) + n
        = succ (m + n)          := by rw [succ_add m n]
      _ = succ (n + m)          := by rw [comm_add m n]
      _ = n + (succ m)          := rfl

class Zero (α : Type) where
  zero : α

instance : Zero Nat where
  zero := Nat.zero

instance [Zero α] : OfNat α 0 where
  ofNat := Zero.zero

class Monoid (α : Type) extends Add α, Zero α where
  add_assoc : ∀ (m n p : Nat), (m + n) + p = m + (n + p)
  add_zero : ∀ (n : Nat), n + 0 = n
  zero_add : ∀ (n : Nat), 0 + n = n

instance : Monoid Nat where
  add_assoc := Nat.add_assoc
  add_zero := Nat.add_zero
  zero_add := Nat.zero_add

-- exercises

theorem left_distrib (m n p : Nat) :
  m * (n + p) = m * n + m * p := sorry


section propositional

variables P Q R : Prop

------------------------------------------------
-- Proposições de dupla negaço:
------------------------------------------------

theorem doubleneg_intro :
  P → ¬¬P  :=
begin
  intro p,
  intro np,
  apply np,
  exact p,
end

theorem doubleneg_elim :
  ¬¬P → P  :=
begin
  intro nnp,
  by_contradiction np,
  exact nnp np,
end

theorem doubleneg_law :
  ¬¬P ↔ P  :=
begin
  split,
  exact doubleneg_elim P,
  exact doubleneg_intro P,
end

------------------------------------------------
-- Comutatividade dos ∨,∧:
------------------------------------------------

theorem disj_comm :
  (P ∨ Q) → (Q ∨ P)  :=
begin
  intro pq,
  cases pq with p q,
  right,
  exact p,
  left,
  exact q,
end

theorem conj_comm :
  (P ∧ Q) → (Q ∧ P)  :=
begin
  intro pq,
  cases pq with p q,
  split,
  exact q,
  exact p,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos →,∨:
------------------------------------------------

theorem impl_as_disj_converse :
  (¬P ∨ Q) → (P → Q)  :=
begin
  intro npq,
  intro p,
  cases npq with np q,
  exfalso,
  exact np p,
  exact q,
end

theorem disj_as_impl :
  (P ∨ Q) → (¬P → Q)  :=
begin
  intros pq np,
  cases pq with p q,
  exfalso,
  exact np p,
  exact q,
end

------------------------------------------------
-- Proposições de contraposição:
------------------------------------------------

theorem impl_as_contrapositive :
  (P → Q) → (¬Q → ¬P)  :=
begin
  intros hpq nq p,
  apply nq,
  apply hpq,
  exact p,
end

theorem impl_as_contrapositive_converse :
  (¬Q → ¬P) → (P → Q)  :=
begin
  intros nqp p,
  by_contradiction,
  have np := nqp h,
  apply np,
  exact p,
end

theorem contrapositive_law :
  (P → Q) ↔ (¬Q → ¬P)  :=
begin
  split,
  exact impl_as_contrapositive P Q,
  exact impl_as_contrapositive_converse P Q,
end

------------------------------------------------
-- A irrefutabilidade do LEM:
------------------------------------------------

theorem lem_irrefutable :
  ¬¬(P∨¬P)  :=
begin
  intro nnp,
  apply nnp,
  right,
  intro p,
  apply nnp,
  left,
  exact p,
end

------------------------------------------------
-- A lei de Peirce
------------------------------------------------

theorem peirce_law_weak :
  ((P → Q) → P) → ¬¬P  :=
begin
  intros pqp np,
  have h : P → Q,
  intro p,
  exfalso,
  apply np,
  exact p,
  apply np,
  have p := pqp h,
  exact p,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∨,∧:
------------------------------------------------

theorem disj_as_negconj :
  P∨Q → ¬(¬P∧¬Q)  :=
begin
  intros pq npq,
  cases pq with p q,
  cases npq with np nq,
  contradiction,
  cases npq with np nq,
  contradiction,
end

theorem conj_as_negdisj :
  P∧Q → ¬(¬P∨¬Q)  :=
begin
  intros pq npq,
  cases npq with np nq,
  cases pq with p q,
  contradiction,
  cases pq with p q,
  contradiction,
end

------------------------------------------------
-- As leis de De Morgan para ∨,∧:
------------------------------------------------

theorem demorgan_disj :
  ¬(P∨Q) → (¬P ∧ ¬Q)  :=
begin
  intro npq,
  split,
  intro p,
  apply npq,
  left,
  exact p,
  intro q,
  apply npq,
  right,
  exact q,
end

theorem demorgan_disj_converse :
  (¬P ∧ ¬Q) → ¬(P∨Q)  :=
begin
  intro npnq,
  intro pq,
  cases npnq with np nq,
  cases pq with p q,
  apply np,
  exact p,
  apply nq,
  exact q,
end

theorem demorgan_conj :
  ¬(P∧Q) → (¬Q ∨ ¬P)  :=
begin
  intro h,
  by_contradiction nh,
  apply h,
  split,
  by_contradiction npu,
  apply nh,
  right,
  exact npu,
  by_contradiction npu,
  apply nh,
  left,
  exact npu,
end

theorem demorgan_conj_converse :
  (¬Q ∨ ¬P) → ¬(P∧Q)  :=
begin
  intros nqnp pq,
  cases nqnp with nq np,
  cases pq with p q,
  apply nq,
  exact q,
  cases pq with p q,
  apply np,
  exact p,
end

theorem demorgan_conj_law :
  ¬(P∧Q) ↔ (¬Q ∨ ¬P)  :=
begin
  split,
  exact demorgan_conj P Q,
  exact demorgan_conj_converse P Q,
end

theorem demorgan_disj_law :
  ¬(P∨Q) ↔ (¬P ∧ ¬Q)  :=
begin
  split,
  exact demorgan_disj P Q,
  exact demorgan_disj_converse P Q,
end

------------------------------------------------
-- Proposições de distributividade dos ∨,∧:
------------------------------------------------

theorem distr_conj_disj :
  P∧(Q∨R) → (P∧Q)∨(P∧R)  :=
begin
  intro p_and_q_or_r,
  cases p_and_q_or_r with p q_or_r,
  cases q_or_r with q r,
  left,
  split,
  exact p,
  exact q,
  right,
  split,
  exact p,
  exact r,
end

theorem distr_conj_disj_converse :
  (P∧Q)∨(P∧R) → P∧(Q∨R)  :=
begin
  intro pqpr,
  cases pqpr with pq pr,
  cases pq with p q,
  split,
  exact p,
  left,
  exact q,
  cases pr with p r,
  split,
  exact p,
  right,
  exact r,
end

theorem distr_disj_conj :
  P∨(Q∧R) → (P∨Q)∧(P∨R)  :=
begin
  intro pqr,
  cases pqr with p qr,
  split,
  left,
  exact p,
  left,
  exact p,
  cases qr with q r,
  split,
  right,
  exact q,
  right,
  exact r,
end

theorem distr_disj_conj_converse :
  (P∨Q)∧(P∨R) → P∨(Q∧R)  :=
begin
  intro pqpr,
  cases pqpr with pq pr,
  cases pr with p r,
  left,
  exact p,
  cases pq with p q,
  left,
  exact p,
  right,
  split,
  exact q,
  exact r,
end

------------------------------------------------
-- Currificação
------------------------------------------------

theorem curry_prop :
  ((P∧Q)→R) → (P→(Q→R))  :=
begin
  intro pqr,
  intros p q,
  apply pqr,
  split,
  exact p,
  exact q,
end

theorem uncurry_prop :
  (P→(Q→R)) → ((P∧Q)→R)  :=
begin
  intros pqr pq,
  apply pqr,
  cases pq with p q,
  exact p,
  cases pq with p q,
  exact q,
end

------------------------------------------------
-- Reflexividade da →:
------------------------------------------------

theorem impl_refl :
  P → P  :=
begin
  intro p,
  exact p,
end

------------------------------------------------
-- Weakening and contraction:
------------------------------------------------

theorem weaken_disj_right :
  P → (P∨Q)  :=
begin
  intro p,
  left,
  exact p,
end

theorem weaken_disj_left :
  Q → (P∨Q)  :=
begin
  intro q,
  right,
  exact q,
end

theorem weaken_conj_right :
  (P∧Q) → P  :=
begin
  intro pq,
  cases pq with p q,
  exact p,
end

theorem weaken_conj_left :
  (P∧Q) → Q  :=
begin
  intro pq,
  cases pq with p q,
  exact q,
end

theorem conj_idemp :
  (P∧P) ↔ P :=
begin
  split,
  exact weaken_conj_right P P,
  intro p,
  split,
  exact p,
  exact p,
end

theorem disj_idemp :
  (P∨P) ↔ P  :=
begin
  split,
  intro p,
  cases p with p' p'',
  exact p',
  exact p'',
  exact weaken_disj_left P P,
end

end propositional

----------------------------------------------------------------

section predicate

variable U : Type
variables P Q : U -> Prop

------------------------------------------------
-- As leis de De Morgan para ∃,∀:
------------------------------------------------

theorem demorgan_exists :
  ¬(∃x, P x) → (∀x, ¬P x)  :=
begin
  intros h x np,
  apply h,
  existsi x,
  exact np,
end

theorem demorgan_exists_converse :
  (∀x, ¬P x) → ¬(∃x, P x)  :=
begin
  intros h nx,
  cases nx with x hx,
  have xx := h x,
  apply xx,
  exact hx,
end

theorem demorgan_forall :
  ¬(∀x, P x) → (∃x, ¬P x)  :=
begin
  intro h,
  by_contradiction nh,
  apply h,
  intro u,
  by_contradiction npu,
  apply nh,
  existsi u,
  exact npu,
end

theorem demorgan_forall_converse :
  (∃x, ¬P x) → ¬(∀x, P x)  :=
begin
  intros h ax,
  cases h with x hx,
  have xx := ax x,
  apply hx,
  exact xx,
end

theorem demorgan_forall_law :
  ¬(∀x, P x) ↔ (∃x, ¬P x)  :=
begin
  split,
  exact demorgan_forall U P,
  exact demorgan_forall_converse U P,
end

theorem demorgan_exists_law :
  ¬(∃x, P x) ↔ (∀x, ¬P x)  :=
begin
  split,
  exact demorgan_exists U P,
  exact demorgan_exists_converse U P,
end

------------------------------------------------
-- Proposições de interdefinabilidade dos ∃,∀:
------------------------------------------------

theorem exists_as_neg_forall :
  (∃x, P x) → ¬(∀x, ¬P x)  :=
begin
  intros h nh,
  cases h with x hx,
  have xx := nh x,
  apply xx,
  exact hx,
end

theorem forall_as_neg_exists :
  (∀x, P x) → ¬(∃x, ¬P x)  :=
begin
  intros h nh,
  cases nh with x hx,
  have xx := h x,
  apply hx,
  exact xx,
end

theorem forall_as_neg_exists_converse :
  ¬(∃x, ¬P x) → (∀x, P x)  :=
begin
  intros h u,
  by_contradiction npu,
  apply h,
  existsi u,
  exact npu,
end

theorem exists_as_neg_forall_converse :
  ¬(∀x, ¬P x) → (∃x, P x)  :=
begin
  intros h,
  by_contradiction nep,
  apply h,
  intros u npu,
  apply nep,
  existsi u,
  exact npu,
end

theorem forall_as_neg_exists_law :
  (∀x, P x) ↔ ¬(∃x, ¬P x)  :=
begin
  split,
  exact forall_as_neg_exists U P,
  exact forall_as_neg_exists_converse U P,
end

theorem exists_as_neg_forall_law :
  (∃x, P x) ↔ ¬(∀x, ¬P x)  :=
begin
  split,
  exact exists_as_neg_forall U P,
  exact exists_as_neg_forall_converse U P,
end

------------------------------------------------
--  Proposições de distributividade de quantificadores:
------------------------------------------------

theorem exists_conj_as_conj_exists :
  (∃x, P x ∧ Q x) → (∃x, P x) ∧ (∃x, Q x)  :=
begin
  intros h,
  split,
  cases h with x hx,
  cases hx with px qx,
  existsi x,
  exact px,
  cases h with x hx,
  cases hx with px qx,
  existsi x,
  exact qx,
end

theorem exists_disj_as_disj_exists :
  (∃x, P x ∨ Q x) → (∃x, P x) ∨ (∃x, Q x)  :=
begin
  intros h,
  cases h with x hx,
  cases hx with px qx,
  left,
  existsi x,
  exact px,
  right,
  existsi x,
  exact qx,
end

theorem exists_disj_as_disj_exists_converse :
  (∃x, P x) ∨ (∃x, Q x) → (∃x, P x ∨ Q x)  :=
begin
  intros h,
  cases h with epx eqx,
  cases epx with x hx,
  existsi x,
  left,
  exact hx,
  cases eqx with x hx,
  existsi x,
  right,
  exact hx,
end

theorem forall_conj_as_conj_forall :
  (∀x, P x ∧ Q x) → (∀x, P x) ∧ (∀x, Q x)  :=
begin
  intros h,
  split,
  intro x,
  have xx := h x,
  cases xx with px qx,
  exact px,
  intro x,
  have xx := h x,
  cases xx with px qx,
  exact qx,
end

theorem forall_conj_as_conj_forall_converse :
  (∀x, P x) ∧ (∀x, Q x) → (∀x, P x ∧ Q x)  :=
begin
  intros h,
  cases h with apx aqx,
  intro x,
  split,
  exact apx x,
  exact aqx x,
end

theorem forall_disj_as_disj_forall_converse :
  (∀x, P x) ∨ (∀x, Q x) → (∀x, P x ∨ Q x)  :=
begin
  intros h,
  cases h with apx aqx,
  intro x,
  left,
  exact apx x,
  intro x,
  right,
  exact aqx x,
end

/- NOT THEOREMS --------------------------------

theorem forall_disj_as_disj_forall :
  (∀x, P x ∨ Q x) → (∀x, P x) ∨ (∀x, Q x)  :=
begin
end

theorem exists_conj_as_conj_exists_converse :
  (∃x, P x) ∧ (∃x, Q x) → (∃x, P x ∧ Q x)  :=
begin
end

---------------------------------------------- -/

end predicate


import algebra.order.ring

variables {R : Type*} [ordered_ring R]
variables a b c : R

#check sub_nonneg
#check sub_nonneg.mp
#check sub_nonneg.mpr

-- BEGIN
example : 0 ≤ b - a → a ≤ b :=
begin
  intro h,
  rw sub_nonneg at h,
  exact h,
end

/- Alternatively -/
example : 0 ≤ b - a → a ≤ b :=
begin
  intro h,
  exact sub_nonneg.mp h,
end

example : a ≤ b → 0 ≤ b - a :=
begin
  intro h,
  rw ← sub_nonneg at h,
  exact h,
end

/- Alternatively -/
example : a ≤ b → 0 ≤ b - a :=
begin
  intro h,
  exact sub_nonneg.mpr h,
end
-- END


theorem add_zero : x + 0 = x := by
  rfl

theorem add_succ : x + Nat.succ y = Nat.succ (x + y) := by
  rfl

theorem one_Eq_succ_zero : 1 = Nat.succ 0 := by
  rfl

--set_option hygienicIntro false in
theorem zero_add : 0 + x = x := by
  induction x with | succ _ ih => _ | _ => _
  rfl
  rewrite add_succ
  apply congrArg -- or just `rw ih`/`rw v_0`
  assumption

theorem add_assoc (a b c : Nat) : (a + b) + c = a + (b + c) := by
  induction c with | succ _ ih => _ | _ => _
  rewrite add_zero
  rewrite add_zero
  rfl
  rewrite add_succ
  rewrite add_succ
  rewrite add_succ
  rewrite ih
  rfl

theorem succ_add (a b : Nat) : Nat.succ a + b = Nat.succ (a + b) := by
  induction b with | succ _ ih => _ | _ => _
  rfl
  rewrite add_succ
  rewrite add_succ
  rewrite ih
  rfl

theorem add_comm (a b : Nat) : a + b = b + a := by
  induction b with | succ _ ih => _ | _ => _
  rewrite zero_add
  rfl
  rewrite add_succ
  rewrite succ_add
  rewrite ih
  rfl

theorem succ_Eq_add_one (n : Nat) : Nat.succ n = n + 1 := by
  rewrite one_Eq_succ_zero
  rewrite add_succ
  rfl

theorem add_rightComm (a b c : Nat) : a + b + c = a + c + b := by
  rewrite add_assoc
  rewrite add_comm b
  rewrite add_assoc
  rfl


/-
Copyright (c) 2022 Eric Rodriguez. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Rodriguez
-/
import algebra.ring.ulift
import data.mv_polynomial.cardinal
import data.rat.denumerable
import field_theory.finite.galois_field
import logic.equiv.transfer_instance
import ring_theory.localization.cardinality
import set_theory.cardinal.divisibility
import data.nat.factorization.prime_pow

/-!
# Cardinality of Fields

In this file we show all the possible cardinalities of fields. All infinite cardinals can harbour
a field structure, and so can all types with prime power cardinalities, and this is sharp.

## Main statements

* `fintype.nonempty_field_iff`: A `fintype` can be given a field structure iff its cardinality is a
  prime power.
* `infinite.nonempty_field` : Any infinite type can be endowed a field structure.
* `field.nonempty_iff` : There is a field structure on type iff its cardinality is a prime power.

-/

local notation `‖` x `‖` := fintype.card x

open_locale cardinal non_zero_divisors

universe u

/-- A finite field has prime power cardinality. -/
lemma fintype.is_prime_pow_card_of_field {α} [fintype α] [field α] : is_prime_pow (‖α‖) :=
begin
  casesI char_p.exists α with p _,
  haveI hp := fact.mk (char_p.char_is_prime α p),
  let b := is_noetherian.finset_basis (zmod p) α,
  rw [module.card_fintype b, zmod.card, is_prime_pow_pow_iff],
  { exact hp.1.is_prime_pow },
  rw ←finite_dimensional.finrank_eq_card_basis b,
  exact finite_dimensional.finrank_pos.ne'
end

/-- A `fintype` can be given a field structure iff its cardinality is a prime power. -/
lemma fintype.nonempty_field_iff {α} [fintype α] : nonempty (field α) ↔ is_prime_pow (‖α‖) :=
begin
  refine ⟨λ ⟨h⟩, by exactI fintype.is_prime_pow_card_of_field, _⟩,
  rintros ⟨p, n, hp, hn, hα⟩,
  haveI := fact.mk (nat.prime_iff.mpr hp),
  exact ⟨(fintype.equiv_of_card_eq ((galois_field.card p n hn.ne').trans hα)).symm.field⟩,
end

lemma fintype.not_is_field_of_card_not_prime_pow {α} [fintype α] [ring α] :
  ¬ is_prime_pow (‖α‖) → ¬ is_field α :=
mt $ λ h, fintype.nonempty_field_iff.mp ⟨h.to_field⟩

/-- Any infinite type can be endowed a field structure. -/
lemma infinite.nonempty_field {α : Type u} [infinite α] : nonempty (field α) :=
begin
  letI K := fraction_ring (mv_polynomial α $ ulift.{u} ℚ),
  suffices : #α = #K,
  { obtain ⟨e⟩ := cardinal.eq.1 this,
    exact ⟨e.field⟩ },
  rw ←is_localization.card (mv_polynomial α $ ulift.{u} ℚ)⁰ K le_rfl,
  apply le_antisymm,
  { refine ⟨⟨λ a, mv_polynomial.monomial (finsupp.single a 1) (1 : ulift.{u} ℚ), λ x y h, _⟩⟩,
    simpa [mv_polynomial.monomial_eq_monomial_iff, finsupp.single_eq_single_iff] using h },
  { simpa using @mv_polynomial.cardinal_mk_le_max α (ulift.{u} ℚ) _ }
end

/-- There is a field structure on type if and only if its cardinality is a prime power. -/
lemma field.nonempty_iff {α : Type u} : nonempty (field α) ↔ is_prime_pow (#α) :=
begin
  rw cardinal.is_prime_pow_iff,
  casesI fintype_or_infinite α with h h,
  { simpa only [cardinal.mk_fintype, nat.cast_inj, exists_eq_left',
        (cardinal.nat_lt_aleph_0 _).not_le, false_or]
      using fintype.nonempty_field_iff },
  { simpa only [← cardinal.infinite_iff, h, true_or, iff_true]
      using infinite.nonempty_field },
end


/-
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 topology.continuous_function.compact -- continuous functions, compact sets

/-!

# Topology : continuous image of compact is compact

Useful API for this proof:

* `continuous.is_open_preimage` : preimage of open under continuous map is open

-/

-- let X & Y be topological spaces
variables (X Y : Type) [topological_space X] [topological_space Y]

-- let S be a subset of X
variable (S : set X)

-- Let `f : X → Y` be a function
variables (f : X → Y)

-- If f is continuous and S is compact, then the image f(S) is also compact.
example (hf : continuous f) (hS : is_compact S) : is_compact (f '' S) :=
begin
  -- use defn of compact
  rw is_compact_iff_finite_subcover at hS ⊢,
  -- cover f(S) with opens U_i
  intros I U hUopen hUfS,
  -- f⁻¹(U_i) are an open cover of S
  specialize hS (λ i, f ⁻¹' U i) _ _,
  { intro j,
    exact continuous.is_open_preimage hf (U j) (hUopen j) }, -- open
  { finish }, -- cover
  -- so have a finite subcover f⁻¹(U_1), f⁻¹(U_2), ... f⁻¹(U_n)
  -- and Lean can take it from there
  finish,
end


import .love01_definitions_and_statements_demo

/-! # LoVe Exercise 1: Definitions and Statements

Replace the placeholders (e.g., `:= sorry`) with your solutions. -/

set_option pp.beta true

namespace LoVe

/-! ## Question 1: Truncating Subtraction

1.1. Define the function `sub` that implements truncating subtraction on natural
numbers by recursion. "Truncating" means that results that mathematically would
be negative are represented by 0. For example:

    `sub 7 2 = 5`
    `sub 2 7 = 0` -/

def sub : ℕ → ℕ → ℕ
    | nat.zero n    := nat.zero
    | m nat.zero     := m
    | (nat.succ m) (nat.succ n) := sub m n

/-! 1.2. Check that your function works as expected. -/

#eval sub 0 0   -- expected: 0
#eval sub 0 1   -- expected: 0
#eval sub 0 7   -- expected: 0
#eval sub 1 0   -- expected: 1
#eval sub 1 1   -- expected: 0
#eval sub 3 0   -- expected: 3
#eval sub 2 7   -- expected: 0
#eval sub 3 1   -- expected: 2
#eval sub 3 3   -- expected: 0
#eval sub 3 7   -- expected: 0
#eval sub 7 2   -- expected: 5

/-! ## Question 2: Arithmetic Expressions

Consider the type `aexp` from the lecture and the function `eval` that
computes the value of an expression. You will find the definitions in the file
`love01_definitions_and_statements_demo.lean`. One way to find them quickly is
to

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

#check aexp
#check eval

/-! 2.1. Test that `eval` behaves as expected. Make sure to exercise each
constructor at least once. You can use the following environment in your tests.
What happens if you divide by zero?

Make sure to use `#eval`. For technical reasons, `#reduce` does not work well
here. Note that `#eval` (Lean's evaluation command) and `eval` (our evaluation
function on `aexp`) are unrelated. -/

def some_env : string → ℤ
| "x" := 3
| "y" := 17
| _   := 0

#eval eval some_env (aexp.var "x")   -- expected: 3
#eval eval some_env ((aexp.div (aexp.var "x") (aexp.num 0))

/-! 2.2. The following function simplifies arithmetic expressions involving
addition. It simplifies `0 + e` and `e + 0` to `e`. Complete the definition so
that it also simplifies expressions involving the other three binary
operators. -/

def simplify : aexp → aexp
| (aexp.add (aexp.num 0) e₂) := simplify e₂
| (aexp.add e₁ (aexp.num 0)) := simplify e₁
-- insert the missing cases here
| (aexp.mul (aexp.num 1) e₂) := simplify e₂
| (aexp.mul e₁ (aexp.num 1)) := simplify e₁
-- catch-all cases below
| (aexp.num i)               := aexp.num i
| (aexp.var x)               := aexp.var x
| (aexp.add e₁ e₂)           := aexp.add (simplify e₁) (simplify e₂)
| (aexp.sub e₁ e₂)           := aexp.sub (simplify e₁) (simplify e₂)
| (aexp.mul e₁ e₂)           := aexp.mul (simplify e₁) (simplify e₂)
| (aexp.div e₁ e₂)           := aexp.div (simplify e₁) (simplify e₂)

/-! 2.3. State (without proving it) the correctness lemma for `simplify`, namely
that the simplified expression should have the same semantics, with respect to
`eval`, as the original expression. -/

lemma simplify_correct (env : string → ℤ) (e : aexp) :
-- enter your lemma statement here
    (eval env (simplify(e))) == (eval env e) := sorry

/-! ## Question 3: λ-Terms

3.1. Complete the following definitions, by replacing the `sorry` markers by
terms of the expected type.

Hint: A procedure for doing so systematically is described in Section 1.1.4 of
the Hitchhiker's Guide. As explained there, you can use `_` as a placeholder
while constructing a term. By hovering over `_`, you will see the current
logical context. -/

def I : α → α :=
λa, a

def K : α → β → α :=
λa b, a

def C : (α → β → γ) → β → α → γ :=
λf b a, f a b

def proj_1st : α → α → α :=
λ a b, a

/-! Please give a different answer than for `proj_1st`. -/

def proj_2nd : α → α → α :=
λ a b, b

def some_nonsense : (α → β → γ) → α → (α → γ) → β → γ :=
λ f a g b, f a b

/-! 3.2. Show the typing derivation for your definition of `C` above, on paper
or using ASCII or Unicode art. You might find the characters `–` (to draw
horizontal bars) and `⊢` useful. -/

-- write your solution in a comment here or on paper

end LoVe


/-
Copyright (c) 2021 OpenAI. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kunhao Zheng, Stanislas Polu, David Renshaw, OpenAI GPT-f
-/
import mathzoo.imports.miniF2F

open_locale nat rat real big_operators topological_space

/--
What is the difference between the sum of the first 2003 even counting
numbers and the sum of the first 2003 odd counting numbers?
Answer: 2003
--/
theorem amc12a_2003_p1
  (u v : ℕ → ℕ)
  (h₀ : ∀ n, u n = 2 * n + 2)
  (h₁ : ∀ n, v n = 2 * n + 1) :
  (∑ k in finset.range(2003), u k) - (∑ k in finset.range(2003), v k) = 2003 :=
begin
  simp [finset.range, h₀, h₁],
  simp [*, nat.add_sub_add_left, mul_comm],
  norm_num [h₁],
end


-- ---------------------------------------------------------------------
-- Ejercicio 1. Demostrar que si a, b, c, d, e y f son números reales
-- tales que
--    b * c = e * f
-- entonces
--    ((a * b) * c) * d = ((a * e) * f) * d
-- ---------------------------------------------------------------------

import data.real.basic

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

example
  (a b c d e f : ℝ)
  (h : b * c = e * f)
  : ((a * b) * c) * d = ((a * e) * f) * d :=
begin
  rw mul_assoc a,
  rw h,
  rw ←mul_assoc a,
end

-- El desarrollo de la prueba es
--
-- inicio
--    a b c d e f : ℝ,
--    h : b * c = e * f
--    ⊢ (a * (b * c)) * d = ((a * e) * f) * d
-- rw mul_assoc a,
--    S
--    ⊢ a * (b * c) * d = a * e * f * d
-- rw h,
--    S
--    ⊢ a * (e * f) * d = a * e * f * d
-- rw ←mul_assoc a,
--    no goals
--
-- En el desarrollo anterior, S es el conjunto de hipótesis; es decir,
--    S = {a b c d e f : ℝ,
--         h : b * c = e * f}

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

example
  (a b c d e f : ℝ)
  (h : b * c = e * f)
  : ((a * b) * c) * d = ((a * e) * f) * d :=
calc
  ((a * b) * c) * d = (a * (b * c)) * d : by rw mul_assoc a
                ... = (a * (e * f)) * d : by rw h
                ... = ((a * e) * f) * d : by rw ←mul_assoc a

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

example
  (a b c d e f : ℝ)
  (h : b * c = e * f)
  : ((a * b) * c) * d = ((a * e) * f) * d :=
by finish

-- ---------------------------------------------------------------------
-- Ejercicio 2. Demostrar que si a, b, c y d son números reales tales
-- que
--    c = b * a - d
--    d = a * b
-- entonces
--    c = 0
-- ---------------------------------------------------------------------

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

example
  (a b c d : ℝ)
  (h1 : c = b * a - d)
  (h2 : d = a * b)
  : c = 0 :=
begin
  rw h1,
  rw mul_comm,
  rw h2,
  rw sub_self,
end

-- Comentario: El último lema se puede encontrar escribiendo previamente
--    by library_search
-- y afirma que
--    ∀ (a : G), a - a = 0

-- Desarrollo de la prueba:
--
-- inicio
--    a b c d : ℝ,
--    h1 : c = b * a - d,
--    h2 : d = a * b
--    ⊢ c = 0
-- rw h1,
--    S
--    ⊢ b * a - d = 0
-- rw mul_comm,
--    S
--    ⊢ a * b - d = 0
-- rw h2,
--    S
--    ⊢ a * b - a * b = 0
-- rw sub_self,
--    no goals
--
-- En el desarrollo anterior, S es el conjunto de hipótesis; es decir,
--    S = {a b c d : ℝ,
--         h1 : c = b * a - d,
--         h2 : d = a * b}

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

example
  (a b c d : ℝ)
  (h1 : c = b * a - d)
  (h2 : d = a * b)
  : c = 0 :=
calc
  c   = b * a - d     : by rw h1
  ... = a * b - d     : by rw mul_comm
  ... = a * b - a * b : by rw h2
  ... = 0             : by rw sub_self

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

example
  (a b c d : ℝ)
  (h1 : c = b * a - d)
  (h2 : d = a * b)
  : c = 0 :=
by finish [mul_comm]


-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que en los retículos se verifica que
--     x ⊔ y = y ⊔ x
-- ----------------------------------------------------------------------

import order.lattice

variables {α : Type*} [lattice α]
variables x y z : α

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

lemma aux1 : x ⊔ y ≤ y ⊔ x :=
begin
  have h1 : x ≤ y ⊔ x,
    by exact le_sup_right,
  have h2 : y ≤ y ⊔ x,
    by exact le_sup_left,
  show x ⊔ y ≤ y ⊔ x,
    by exact sup_le h1 h2,
end

example : x ⊔ y = y ⊔ x :=
begin
  have h1 : x ⊔ y ≤ y ⊔ x,
    by exact aux1 x y,
  have h2 : y ⊔ x ≤ x ⊔ y,
    by exact aux1 y x,
  show x ⊔ y = y ⊔ x,
    by exact le_antisymm h1 h2,
end

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

lemma aux2 : x ⊔ y ≤ y ⊔ x :=
sup_le le_sup_right le_sup_left

example : x ⊔ y = y ⊔ x :=
le_antisymm (aux2 x y) (aux2 y x)

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

lemma aux : x ⊔ y ≤ y ⊔ x :=
begin
  apply sup_le,
  apply le_sup_right,
  apply le_sup_left,
end

-- Su desarrollo es
--
-- ⊢ x ⊔ y ≤ y ⊔ x
--    apply sup_le,
-- ⊢ x ≤ y ⊔ x
-- |   apply le_sup_right,
-- ⊢ y ≤ y ⊔ x
-- |   apply le_sup_left,
-- no goals

example : x ⊔ y = y ⊔ x :=
begin
  apply le_antisymm,
  apply aux,
  apply aux,
end

-- Su desarrollo es
--
-- ⊢ x ⊔ y = y ⊔ x
--    apply le_antisymm,
-- ⊢ x ⊔ y ≤ y ⊔ x
-- |   apply aux,
-- ⊢ y ⊔ x ≤ x ⊔ y
-- |   apply aux,
-- no goals

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

example : x ⊔ y = y ⊔ x :=
by apply le_antisymm; simp

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

example : x ⊔ y = y ⊔ x :=
-- by library_search
sup_comm

-- 6ª demostración
-- ===============

example : x ⊔ y = y ⊔ x :=
-- by hint
by finish

-- Lemas usados
-- ============

-- #check (sup_comm : x ⊔ y = y ⊔ x)
-- #check (le_sup_left : x ≤ x ⊔ y)
-- #check (le_sup_right : y ≤ x ⊔ y)
-- #check (le_antisymm : x ≤ y → y ≤ x → x = y)
-- #check (sup_le : x ≤ z → y ≤ z → x ⊔ y ≤ z)


-- Negación del universal: Caracterización de funciones no pares
-- =============================================================

import data.real.basic

variable (f : ℝ → ℝ)

-- ----------------------------------------------------
-- Ejercicio 1. Definir la función
--    par : (ℝ → ℝ) → Prop
-- tal que (par f) expresa que f es par.
-- ----------------------------------------------------

def par : (ℝ → ℝ) → Prop
| f := ∀ x, f (-x) = f x

-- ----------------------------------------------------
-- Ejercicio 2. Demostrar que
--    ¬par f ↔ ∃ x, f (-x) ≠ f x
-- ----------------------------------------------------

-- 1ª demostración
example : ¬par f ↔ ∃ x, f (-x) ≠ f x :=
begin
  split,
  { contrapose,
    intro h1,
    rw not_not,
    unfold par,
    intro x,
    by_contradiction h2,
    apply h1,
    use x, },
  { intro h1,
    intro h2,
    unfold par at h2,
    cases h1 with x hx,
    apply hx,
    exact h2 x, },
end

-- 2ª demostración
example : ¬par f ↔ ∃ x, f (-x) ≠ f x :=
begin
  split,
  { contrapose,
    intro h1,
    rw not_not,
    intro x,
    by_contradiction h2,
    apply h1,
    use x, },
  { rintros ⟨x, hx⟩ h',
    exact hx (h' x) },
end

-- 3ª demostración
example : ¬par f ↔ ∃ x, f (-x) ≠ f x :=
begin
  unfold par,
  push_neg,
end

-- 4ª demostración
example : ¬par f ↔ ∃ x, f (-x) ≠ f x :=
iff.intro
  ( have h1 : ¬(∃ x, f (-x) ≠ f x) → ¬¬par f,
      { assume h2 : ¬(∃ x, f (-x) ≠ f x),
        have h3 : par f,
          { assume x,
            have h4 : ¬(f (-x) ≠ f x),
              { assume h5 : f (-x) ≠ f x,
                have h6 : ∃ x, f (-x) ≠ f x,
                  from exists.intro x h5,
                show false,
                  from h2 h6, },
            show f (-x) = f x,
              from not_not.mp h4},
        show ¬¬par f,
          from not_not.mpr h3 },
    show ¬par f → (∃ x, f (-x) ≠ f x),
      from not_imp_not.mp h1)
  ( assume h1 : ∃ x, f (-x) ≠ f x,
    show ¬par f,
      { assume h2 : par f,
        show false, from
          exists.elim h1
            ( assume x,
              assume hx : f (-x) ≠ f x,
              have h3 : f (-x) = f x,
                from h2 x,
              show false,
                from hx h3)})

-- 5ª demostración
example : ¬par f ↔ ∃ x, f (-x) ≠ f x :=
-- by suggest
not_forall

-- 6ª demostración
example : ¬par f ↔ ∃ x, f (-x) ≠ f x :=
-- by hint
by simp [par]


import .love05_inductive_predicates_demo

/- # LoVe Demo 12: Basic Mathematical Structures

We introduce definitions and proofs about basic mathematical structures such as
groups, fields, and linear orders. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Type Classes over a Single Binary Operator

Mathematically, a __group__ is a set `G` with a binary operator `• : G × G → G`
with the following properties, called __group axioms__:

* Associativity: For all `a, b, c ∈ G`, we have `(a • b) • c = a • (b • c)`.
* Identity element: There exists an element `e ∈ G` such that for all `a ∈ G`,
  we have `e • a = a`.
* Inverse element: For each `a ∈ G`, there exists an inverse element
  `inv(a) ∈ G` such that `inv(a) • a = e`.

Examples of groups are
* `ℤ` with `+`;
* `ℝ` with `+`;
* `ℝ \ {0}` with `*`.

In Lean, a type class for groups can be defined as follows: -/

namespace monolithic_group

@[class] structure group (α : Type) :=
(mul          : α → α → α)
(one          : α)
(inv          : α → α)
(mul_assoc    : ∀a b c, mul (mul a b) c = mul a (mul b c))
(one_mul      : ∀a, mul one a = a)
(mul_left_inv : ∀a, mul (inv a) a = one)

end monolithic_group

/- In Lean, however, group is part of a larger hierarchy of algebraic
structures:

Type class               | Properties                               | Examples
------------------------ | -----------------------------------------|-------------------
`semigroup`              | associativity of `*`                     | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`monoid`                 | `semigroup` with unit `1`                | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`left_cancel_semigroup`  | `semigroup` with `c * a = c * b → a = b` |
`right_cancel_semigroup` | `semigroup` with `a * c = b * c → a = b` |
`group`                  | `monoid` with inverse `⁻¹`               |

Most of these structures have commutative versions: `comm_semigroup`,
`comm_monoid`, `comm_group`.

The __multiplicative__ structures (over `*`, `1`, `⁻¹`) are copied to produce
__additive__ versions (over `+`, `0`, `-`):

Type class                   | Properties                                   | Examples
---------------------------- | ---------------------------------------------|-------------------
`add_semigroup`              | associativity of `+`                         | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_monoid`                 | `add_semigroup` with unit `0`                | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_left_cancel_semigroup`  | `add_semigroup` with `c + a = c + b → a = b` | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_right_cancel_semigroup` | `add_semigroup` with `a + c = b + c → a = b` | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`add_group`                  | `add_monoid` with inverse `-`                | `ℝ`, `ℚ`, `ℤ` -/

#print group
#print add_group

/- Let us define our own type, of integers modulo 2, and register it as an
additive group. -/

inductive ℤ₂ : Type
| zero
| one

def ℤ₂.add : ℤ₂ → ℤ₂ → ℤ₂
| ℤ₂.zero a       := a
| a       ℤ₂.zero := a
| ℤ₂.one  ℤ₂.one  := ℤ₂.zero

@[instance] def ℤ₂.add_group : add_group ℤ₂ :=
{ add          := ℤ₂.add,
  add_assoc    :=
    by intros a b c; cases' a; cases' b; cases' c; refl,
  zero         := ℤ₂.zero,
  zero_add     := by intro a; cases' a; refl,
  add_zero     := by intro a; cases' a; refl,
  neg          := λa, a,
  add_left_neg := by intro a; cases' a; refl }

#reduce ℤ₂.one + 0 - 0 - ℤ₂.one

lemma ℤ₂.add_right_neg:
  ∀a : ℤ₂, a + - a = 0 :=
add_right_neg

/- Another example: Lists are an `add_monoid`: -/

@[instance] def list.add_monoid {α : Type} :
  add_monoid (list α) :=
{ zero      := [],
  add       := (++),
  add_assoc := list.append_assoc,
  zero_add  := list.nil_append,
  add_zero  := list.append_nil }

/- ## Type Classes with Two Binary Operators

Mathematically, a __field__ is a set `F` such that

* `F` forms a commutative group under an operator `+`, called addition, with
  identity element `0`.
* `F\{0}` forms a commutative group under an operator `*`, called
  multiplication.
* Multiplication distributes over addition—i.e.,
  `a * (b + c) = a * b + a * c` for all `a, b, c ∈ F`.

In Lean, fields are also part of a larger hierarchy:

Type class       |  Properties                                         | Examples
-----------------|-----------------------------------------------------|-------------------
`semiring`       | `monoid` and `add_comm_monoid` with distributivity  | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`comm_semiring`  | `semiring` with commutativity of `*`                | `ℝ`, `ℚ`, `ℤ`, `ℕ`
`ring`           | `monoid` and `add_comm_group` with distributivity   | `ℝ`, `ℚ`, `ℤ`
`comm_ring`      | `ring` with commutativity of `*`                    | `ℝ`, `ℚ`, `ℤ`
`division_ring`  | `ring` with multiplicative inverse `⁻¹`             | `ℝ`, `ℚ`
`field`          | `division_ring` with commutativity of `*`           | `ℝ`, `ℚ`
`discrete_field` | `field` with decidable equality and `∀n, n / 0 = 0` | `ℝ`, `ℚ` -/

#print field

/- Let us continue with our example: -/

def ℤ₂.mul : ℤ₂ → ℤ₂ → ℤ₂
| ℤ₂.one  a       := a
| a       ℤ₂.one  := a
| ℤ₂.zero ℤ₂.zero := ℤ₂.zero

@[instance] def ℤ₂.field : field ℤ₂ :=
{ one            := ℤ₂.one,
  mul            := ℤ₂.mul,
  inv            := λa, a,
  add_comm       := by intros a b; cases' a; cases' b; refl,
  exists_pair_ne :=
    by apply exists.intro ℤ₂.zero; apply exists.intro ℤ₂.one;
      finish,
  one_mul        := by intros a; cases' a; refl,
  mul_one        := by intros a; cases' a; refl,
  mul_inv_cancel := by intros a h; cases' a; finish,
  inv_zero       := by refl,
  mul_assoc      :=
    by intros a b c; cases' a; cases' b; cases' c; refl,
  mul_comm       := by intros a b; cases' a; cases' b; refl,
  left_distrib   :=
    by intros a b c; cases' a; cases' b; cases' c; refl,
  right_distrib  :=
    by intros a b c; cases' a; cases' b; cases' c; refl,
  ..ℤ₂.add_group }

#reduce (1 : ℤ₂) * 0 / (0 - 1)

#reduce (3 : ℤ₂)

lemma ring_example (a b : ℤ₂) :
  (a + b) ^ 3 = a ^ 3 + 3 * a ^ 2 * b + 3 * a * b ^ 2 + b ^ 3 :=
by ring

lemma ring_exp_example (a b : ℤ₂) (n : ℕ):
  (a + b) ^ (2 + n) =
  (a + b) ^ n * (a ^ 2 + 2 * a * b + b ^ 2) :=
by ring_exp

/- `ring` and `ring_exp` prove equalities over commutative rings and semirings
by normalizing expressions. The `ring_exp` variant also normalizes exponents. -/

lemma abel_example (a b : ℤ) :
  a + b + 0 - (b + a + a) = - a :=
by abel

/- `abel` proves equalities over additive commutative monoids and groups by
normalizing expressions.

## Coercions

When combining numbers form `ℕ`, `ℤ`, `ℚ`, and `ℝ`, we might want to cast from
one type to another. Lean has a mechanism to automatically introduce coercions,
represented by `coe` (syntactic sugar: `↑`). `coe` can be set up to provide
implicit coercions between arbitrary types.

Many coercions are already in place, including the following:

* `coe : ℕ → α` casts `ℕ` to another semiring `α`;
* `coe : ℤ → α` casts `ℤ` to another ring `α`;
* `coe : ℚ → α` casts `ℚ` to another division ring `α`.

For example, this works, although negation `- n` is not defined on natural
numbers: -/

lemma neg_mul_neg_nat (n : ℕ) (z : ℤ) :
  (- z) * (- n) = z * n :=
neg_mul_neg z n

/- Notice how Lean introduced a `↑` coercion: -/

#print neg_mul_neg_nat

/- Another example: -/

lemma neg_nat_mul_neg (n : ℕ) (z : ℤ) :
  (- n : ℤ) * (- z) = n * z :=
neg_mul_neg n z

#print neg_nat_mul_neg

/- In proofs involving coercions, the tactic `norm_cast` can be convenient. -/

lemma norm_cast_example_1 (m n : ℕ) (h : (m : ℤ) = (n : ℤ)) :
  m = n :=
begin
  norm_cast at h,
  exact h
end

lemma norm_cast_example_2 (m n : ℕ) :
  (m : ℤ) + (n : ℤ) = ((m + n : ℕ) : ℤ) :=
by norm_cast

/- `norm_cast` moves coercions towards the inside of expressions, as a form of
simplification. Like `simp`, it will generally produce a subgoal.

`norm_cast` relies on lemmas such as the following: -/

#check nat.cast_add
#check int.cast_add
#check rat.cast_add

/- ### Lists, Multisets and Finite Sets

For finite collections of elements different structures are available:

* lists: order and duplicates matter;
* multisets: only duplicates matter;
* finsets: neither order nor duplicates matter. -/

lemma list_duplicates_example :
  [2, 3, 3, 4] ≠ [2, 3, 4] :=
dec_trivial

lemma list_order_example :
  [4, 3, 2] ≠ [2, 3, 4] :=
dec_trivial

lemma multiset_duplicates_example :
  ({2, 3, 3, 4} : multiset ℕ) ≠ {2, 3, 4} :=
dec_trivial

lemma multiset_order_example :
  ({2, 3, 4} : multiset ℕ) = {4, 3, 2} :=
dec_trivial

lemma finset_duplicates_example :
  ({2, 3, 3, 4} : finset ℕ) = {2, 3, 4} :=
dec_trivial

lemma finsetorder_example :
  ({2, 3, 4} : finset ℕ) = {4, 3, 2} :=
dec_trivial

/- `dec_trivial` is a special lemma that can be used on trivial decidable
goals (e.g., true closed executable expressions). -/

def list.elems : btree ℕ → list ℕ
| btree.empty        := []
| (btree.node a l r) := a :: list.elems l ++ list.elems r

def multiset.elems : btree ℕ → multiset ℕ
| btree.empty        := ∅
| (btree.node a l r) :=
  {a} ∪ (multiset.elems l ∪ multiset.elems r)

def finset.elems : btree ℕ → finset ℕ
| btree.empty        := ∅
| (btree.node a l r) := {a} ∪ (finset.elems l ∪ finset.elems r)

#eval list.sum [2, 3, 4]                          -- result: 9
#eval multiset.sum ({2, 3, 4} : multiset ℕ)       -- result: 9
#eval finset.sum ({2, 3, 4} : finset ℕ) (λn, n)   -- result: 9

#eval list.prod [2, 3, 4]                         -- result: 24
#eval multiset.prod ({2, 3, 4} : multiset ℕ)      -- result: 24
#eval finset.prod ({2, 3, 4} : finset ℕ) (λn, n)  -- result: 24

/- ## Order Type Classes

Many of the structures introduced above can be ordered. For example, the
well-known order on the natural numbers can be defined as follows: -/

inductive nat.le : ℕ → ℕ → Prop
| refl : ∀a : ℕ, nat.le a a
| step : ∀a b : ℕ, nat.le a b → nat.le a (b + 1)

/- This is an example of a linear order. A __linear order__ (or
__total order__) is a binary relation `≤` such that for all `a`, `b`, `c`, the
following properties hold:

* Reflexivity: `a ≤ a`.
* Transitivity: If `a ≤ b` and `b ≤ c`, then `a ≤ c`.
* Antisymmetry: If `a ≤ b` and `b ≤ a`, then `a = b`.
* Totality: `a ≤ b` or `b ≤ a`.

If a relation has the first three properties, it is a __partial order__. An
example is `⊆` on sets, finite sets, or multisets. If a relation has the first
two properties, it is a __preorder__. An example is comparing lists by their
length.

In Lean, there are type classes for these different kinds of orders:
`linear_order`, `partial_order`, and `preorder`. The `preorder` class has the
fields

    `le       : α → α → Prop`
    `le_refl  : ∀a : α, le a a`
    `le_trans : ∀a b c : α, le a b → le b c → le a c`

The `partial_order` class also has

    `le_antisymm : ∀a b : α, le a b → le b a → a = b`

and `linear_order` also has

    `le_total : ∀a b : α, le a b ∨ le b a`

We can declare the preorder on lists that compares lists by their length as
follows: -/

@[instance] def list.length.preord {α : Type} :
  preorder (list α) :=
{ le       := λxs ys, list.length xs ≤ list.length ys,
  le_refl  := by intro xs; exact nat.le_refl _,
  le_trans := by intros xs ys zs; exact nat.le_trans }

/- This instance introduces the infix syntax `≤` and the relations `≥`, `<`,
and `>`: -/

lemma list.length.preord_example {α : Type} (c : α) :
  [c] > [] :=
dec_trivial

/- Complete lattices (lecture 10) are formalized as another type class,
`complete_lattice`, which inherits from `partial_order`.

Type classes combining orders and algebraic structures are also available:

    `ordered_cancel_comm_monoid`
    `ordered_comm_group ordered_semiring`
    `linear_ordered_semiring`
    `linear_ordered_comm_ring`
    `linear_ordered_field`

All these mathematical structures relate `≤` and `<` with `0`, `1`, `+`, and `*`
by monotonicity rules (e.g., `a ≤ b → c ≤ d → a + c ≤ b + d`) and cancellation
rules (e.g., `c + a ≤ c + b → a ≤ b`). -/

end LoVe


/-
Copyright (c) 2021 Stuart Presnell. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Stuart Presnell
-/
import data.nat.prime
import data.finsupp.multiset
import algebra.big_operators.finsupp

/-!
# Prime factorizations

 `n.factorization` is the finitely supported function `ℕ →₀ ℕ`
 mapping each prime factor of `n` to its multiplicity in `n`.  For example, since 2000 = 2^4 * 5^3,
  * `factorization 2000 2` is 4
  * `factorization 2000 5` is 3
  * `factorization 2000 k` is 0 for all other `k : ℕ`.

## TODO

* As discussed in this Zulip thread:
https://leanprover.zulipchat.com/#narrow/stream/217875/topic/Multiplicity.20in.20the.20naturals
We have lots of disparate ways of talking about the multiplicity of a prime
in a natural number, including `factors.count`, `padic_val_nat`, `multiplicity`,
and the material in `data/pnat/factors`.  Move some of this material to this file,
prove results about the relationships between these definitions,
and (where appropriate) choose a uniform canonical way of expressing these ideas.

* Moreover, the results here should be generalised to an arbitrary unique factorization monoid
with a normalization function, and then deduplicated.  The basics of this have been started in
`ring_theory/unique_factorization_domain`.

* Extend the inductions to any `normalization_monoid` with unique factorization.

-/

open nat finset list finsupp
open_locale big_operators

namespace nat

/-- `n.factorization` is the finitely supported function `ℕ →₀ ℕ`
 mapping each prime factor of `n` to its multiplicity in `n`. -/
noncomputable def factorization (n : ℕ) : ℕ →₀ ℕ := (n.factors : multiset ℕ).to_finsupp

/-! ### Basic facts about factorization -/

@[simp] lemma factorization_prod_pow_eq_self {n : ℕ} (hn : n ≠ 0) : n.factorization.prod pow = n :=
begin
  simp only [←prod_to_multiset, factorization, multiset.coe_prod, multiset.to_finsupp_to_multiset],
  exact prod_factors hn,
end

/-- We can write both `n.factorization p` and `n.factors.count p` to represent the power
of `p` in the factorization of `n`: we declare the former to be the simp-normal form.
However, since `factorization` is a finsupp it's noncomputable.  This theorem can also
be used in reverse to compute values of `factorization n p` when required. -/
@[simp] lemma factors_count_eq {n p : ℕ} : n.factors.count p = n.factorization p :=
by simp [factorization]

lemma eq_of_factorization_eq {a b : ℕ} (ha : a ≠ 0) (hb : b ≠ 0)
  (h : ∀ p : ℕ, a.factorization p = b.factorization p) : a = b :=
eq_of_perm_factors ha hb (by simpa only [list.perm_iff_count, factors_count_eq] using h)

/-- Every nonzero natural number has a unique prime factorization -/
lemma factorization_inj : set.inj_on factorization { x : ℕ | x ≠ 0 } :=
λ a ha b hb h, eq_of_factorization_eq ha hb (λ p, by simp [h])

@[simp] lemma factorization_zero : factorization 0 = 0 :=
by simp [factorization]

@[simp] lemma factorization_one : factorization 1 = 0 :=
by simp [factorization]

/-- The support of `n.factorization` is exactly `n.factors.to_finset` -/
@[simp] lemma support_factorization {n : ℕ} : n.factorization.support = n.factors.to_finset :=
by simpa [factorization, multiset.to_finsupp_support]

lemma factor_iff_mem_factorization {n p : ℕ} : p ∈ n.factorization.support ↔ p ∈ n.factors :=
by simp only [support_factorization, list.mem_to_finset]

lemma prime_of_mem_factorization {n p : ℕ} (hp : p ∈ n.factorization.support) : p.prime :=
prime_of_mem_factors (factor_iff_mem_factorization.mp hp)

lemma pos_of_mem_factorization {n p : ℕ} (hp : p ∈ n.factorization.support) : 0 < p :=
prime.pos (prime_of_mem_factorization hp)

lemma le_of_mem_factorization {n p : ℕ} (h : p ∈ n.factorization.support) : p ≤ n :=
le_of_mem_factors (factor_iff_mem_factorization.mp h)

@[simp]
lemma factorization_eq_zero_of_non_prime (n : ℕ) {p : ℕ} (hp : ¬p.prime) : n.factorization p = 0 :=
not_mem_support_iff.1 (mt prime_of_mem_factorization hp)

lemma factorization_eq_zero_of_lt {n p : ℕ} (h : n < p) : n.factorization p = 0 :=
finsupp.not_mem_support_iff.mp (mt le_of_mem_factorization (not_le_of_lt h))

@[simp] lemma factorization_zero_right (n : ℕ) : n.factorization 0 = 0 :=
factorization_eq_zero_of_non_prime _ not_prime_zero

@[simp] lemma factorization_one_right (n : ℕ) : n.factorization 1 = 0 :=
factorization_eq_zero_of_non_prime _ not_prime_one

lemma dvd_of_factorization_pos {n p : ℕ} (hn : n.factorization p ≠ 0) : p ∣ n :=
dvd_of_mem_factors (factor_iff_mem_factorization.1 (mem_support_iff.2 hn))

lemma prime.factorization_pos_of_dvd {n p : ℕ} (hp : p.prime) (hn : n ≠ 0) (h : p ∣ n) :
  0 < n.factorization p :=
by rwa [←factors_count_eq, count_pos, mem_factors_iff_dvd hn hp]

/-- The only numbers with empty prime factorization are `0` and `1` -/
lemma factorization_eq_zero_iff (n : ℕ) : n.factorization = 0 ↔ n = 0 ∨ n = 1 :=
by simp [factorization, add_equiv.map_eq_zero_iff, multiset.coe_eq_zero]

lemma factorization_eq_zero_iff' (n p : ℕ) :
  n.factorization p = 0 ↔ ¬p.prime ∨ ¬p ∣ n ∨ n = 0 :=
begin
  rw [←not_mem_support_iff, support_factorization, mem_to_finset],
  rcases eq_or_ne n 0 with rfl | hn,
  { simp },
  { simp [hn, nat.mem_factors, not_and_distrib] },
end

/-- For nonzero `a` and `b`, the power of `p` in `a * b` is the sum of the powers in `a` and `b` -/
@[simp] lemma factorization_mul {a b : ℕ} (ha : a ≠ 0) (hb : b ≠ 0) :
  (a * b).factorization = a.factorization + b.factorization :=
by { ext p, simp only [add_apply, ←factors_count_eq,
                       perm_iff_count.mp (perm_factors_mul ha hb) p, count_append] }

lemma factorization_mul_support {a b : ℕ} (ha : a ≠ 0) (hb : b ≠ 0) :
  (a * b).factorization.support = a.factorization.support ∪ b.factorization.support :=
begin
  ext q,
  simp only [finset.mem_union, factor_iff_mem_factorization],
  exact mem_factors_mul ha hb
end

/-- For any `p`, the power of `p` in `n^k` is `k` times the power in `n` -/
@[simp] lemma factorization_pow (n k : ℕ) :
  factorization (n^k) = k • n.factorization :=
begin
  induction k with k ih, { simp },
  rcases eq_or_ne n 0 with rfl | hn, { simp },
  rw [pow_succ, factorization_mul hn (pow_ne_zero _ hn), ih, succ_eq_one_add, add_smul, one_smul],
end

/-- The only prime factor of prime `p` is `p` itself, with multiplicity `1` -/
@[simp] lemma prime.factorization {p : ℕ} (hp : prime p) :
  p.factorization = single p 1 :=
begin
  ext q,
  rw [←factors_count_eq, factors_prime hp, single_apply, count_singleton', if_congr eq_comm];
  refl,
end

/-- For prime `p` the only prime factor of `p^k` is `p` with multiplicity `k` -/
lemma prime.factorization_pow {p k : ℕ} (hp : prime p) :
  factorization (p ^ k) = single p k :=
by simp [hp]

/-- If the factorization of `n` contains just one number `p` then `n` is a power of `p` -/
lemma eq_pow_of_factorization_eq_single {n p k : ℕ} (hn : n ≠ 0)
  (h : n.factorization = finsupp.single p k) : n = p ^ k :=
by { rw [←nat.factorization_prod_pow_eq_self hn, h], simp }

/-- If a product over `n.factorization` doesn't use the multiplicities of the prime factors
then it's equal to the corresponding product over `n.factors.to_finset` -/
lemma prod_factorization_eq_prod_factors {n : ℕ} {β : Type*} [comm_monoid β] (f : ℕ → β) :
  n.factorization.prod (λ p k, f p) = ∏ p in n.factors.to_finset, (f p) :=
by { apply prod_congr support_factorization, simp }

/-- For any `p : ℕ` and any function `g : α → ℕ` that's non-zero on `S : finset α`,
the power of `p` in `S.prod g` equals the sum over `x ∈ S` of the powers of `p` in `g x`.
Generalises `factorization_mul`, which is the special case where `S.card = 2` and `g = id`. -/
lemma factorization_prod {α : Type*} {S : finset α} {g : α → ℕ} (hS : ∀ x ∈ S, g x ≠ 0) :
  (S.prod g).factorization = S.sum (λ x, (g x).factorization) :=
begin
  classical,
  ext p,
  apply finset.induction_on' S, { simp },
  { intros x T hxS hTS hxT IH,
    have hT : T.prod g ≠ 0 := prod_ne_zero_iff.mpr (λ x hx, hS x (hTS hx)),
    simp [prod_insert hxT, sum_insert hxT, ←IH, factorization_mul (hS x hxS) hT] }
end

/-! ### Equivalence between `ℕ+` and `ℕ →₀ ℕ` with support in the primes. -/

/-- Any finsupp `f : ℕ →₀ ℕ` whose support is in the primes is equal to the factorization of
the product `∏ (a : ℕ) in f.support, a ^ f a`. -/
lemma prod_pow_factorization_eq_self {f : ℕ →₀ ℕ} (hf : ∀ (p : ℕ), p ∈ f.support → prime p) :
  (f.prod pow).factorization = f :=
begin
  have h : ∀ x : ℕ, x ∈ f.support → x ^ f x ≠ 0 := λ p hp, pow_ne_zero _ (prime.ne_zero (hf p hp)),
  simp only [finsupp.prod, factorization_prod h],
  nth_rewrite_rhs 0 (sum_single f).symm,
  exact sum_congr rfl (λ p hp, prime.factorization_pow (hf p hp)),
end

lemma eq_factorization_iff {n : ℕ} {f : ℕ →₀ ℕ} (hn : n ≠ 0) (hf : ∀ p ∈ f.support, prime p) :
  f = n.factorization ↔ f.prod pow = n :=
⟨λ h, by rw [h, factorization_prod_pow_eq_self hn],
 λ h, by rw [←h, prod_pow_factorization_eq_self hf]⟩

/-- The equiv between `ℕ+` and `ℕ →₀ ℕ` with support in the primes. -/
noncomputable
def factorization_equiv : ℕ+ ≃ {f : ℕ →₀ ℕ | ∀ p ∈ f.support, prime p} :=
{ to_fun    := λ ⟨n, hn⟩, ⟨n.factorization, λ _, prime_of_mem_factorization⟩,
  inv_fun   := λ ⟨f, hf⟩, ⟨f.prod pow,
    prod_pow_pos_of_zero_not_mem_support (λ H, not_prime_zero (hf 0 H))⟩,
  left_inv  := λ ⟨x, hx⟩, subtype.ext $ factorization_prod_pow_eq_self hx.ne.symm,
  right_inv := λ ⟨f, hf⟩, subtype.ext $ prod_pow_factorization_eq_self hf }

lemma factorization_equiv_apply (n : ℕ+) : (factorization_equiv n).1 = n.1.factorization :=
by { cases n, refl }

lemma factorization_equiv_inv_apply {f : ℕ →₀ ℕ} (hf : ∀ p ∈ f.support, prime p) :
  (factorization_equiv.symm ⟨f, hf⟩).1 = f.prod pow := rfl

/-! ### Factorization and divisibility -/

lemma dvd_of_mem_factorization {n p : ℕ} (h : p ∈ n.factorization.support) : p ∣ n :=
begin
  rcases eq_or_ne n 0 with rfl | hn, { simp },
  simp [←mem_factors_iff_dvd hn (prime_of_mem_factorization h), factor_iff_mem_factorization.mp h],
end

lemma pow_factorization_dvd (n p : ℕ) : p ^ n.factorization p ∣ n :=
begin
  by_cases hp : p.prime, swap, { simp [factorization_eq_zero_of_non_prime n hp] },
  rw ←factors_count_eq,
  apply dvd_of_factors_subperm (pow_ne_zero _ hp.ne_zero),
  rw [hp.factors_pow, list.subperm_ext_iff],
  intros q hq,
  simp [list.eq_of_mem_repeat hq],
end

lemma pow_factorization_le {n : ℕ} (p : ℕ) (hn : n ≠ 0) : p ^ n.factorization p ≤ n :=
le_of_dvd hn.bot_lt (nat.pow_factorization_dvd n p)

lemma div_pow_factorization_ne_zero {n : ℕ} (p : ℕ) (hn : n ≠ 0) :
  n / p ^ n.factorization p ≠ 0 :=
begin
  by_cases pp : nat.prime p,
  { apply mt (nat.div_eq_zero_iff (pow_pos (prime.pos pp) _)).1,
    simp [le_of_dvd hn.bot_lt (nat.pow_factorization_dvd n p)] },
  { simp [nat.factorization_eq_zero_of_non_prime n pp, hn] },
end

lemma factorization_le_iff_dvd {d n : ℕ} (hd : d ≠ 0) (hn : n ≠ 0) :
  d.factorization ≤ n.factorization ↔ d ∣ n :=
begin
  split,
  { intro hdn,
    set K := n.factorization - d.factorization with hK,
    use K.prod pow,
    rw [←factorization_prod_pow_eq_self hn, ←factorization_prod_pow_eq_self hd,
        ←finsupp.prod_add_index' pow_zero pow_add, hK, add_tsub_cancel_of_le hdn] },
  { rintro ⟨c, rfl⟩, rw factorization_mul hd (right_ne_zero_of_mul hn), simp },
end

lemma pow_succ_factorization_not_dvd {n p : ℕ} (hn : n ≠ 0) (hp : p.prime) :
  ¬ p ^ (n.factorization p + 1) ∣ n :=
begin
  intro h,
  rw ←factorization_le_iff_dvd (pow_pos hp.pos _).ne' hn at h,
  simpa [hp.factorization] using h p,
end

lemma factorization_le_factorization_mul_left {a b : ℕ} (hb : b ≠ 0) :
  a.factorization ≤ (a * b).factorization :=
begin
  rcases eq_or_ne a 0 with rfl | ha, { simp },
  rw [factorization_le_iff_dvd ha $ mul_ne_zero ha hb],
  exact dvd.intro b rfl
end

lemma factorization_le_factorization_mul_right {a b : ℕ} (ha : a ≠ 0) :
  b.factorization ≤ (a * b).factorization :=
by { rw mul_comm, apply factorization_le_factorization_mul_left ha }

lemma prime.pow_dvd_iff_le_factorization {p k n : ℕ} (pp : prime p) (hn : n ≠ 0) :
  p ^ k ∣ n ↔ k ≤ n.factorization p :=
by rw [←factorization_le_iff_dvd (pow_pos pp.pos k).ne' hn, pp.factorization_pow, single_le_iff]

lemma prime.pow_dvd_iff_dvd_pow_factorization {p k n : ℕ} (pp : prime p) (hn : n ≠ 0) :
  p ^ k ∣ n ↔ p ^ k ∣ p ^ n.factorization p :=
by rw [pow_dvd_pow_iff_le_right pp.one_lt, pp.pow_dvd_iff_le_factorization hn]

lemma prime.dvd_iff_one_le_factorization {p n : ℕ} (pp : prime p) (hn : n ≠ 0) :
  p ∣ n ↔ 1 ≤ n.factorization p :=
iff.trans (by simp) (pp.pow_dvd_iff_le_factorization hn)

lemma exists_factorization_lt_of_lt {a b : ℕ} (ha : a ≠ 0) (hab : a < b) :
  ∃ p : ℕ, a.factorization p < b.factorization p :=
begin
  have hb : b ≠ 0 := (ha.bot_lt.trans hab).ne',
  contrapose! hab,
  rw [←finsupp.le_def, factorization_le_iff_dvd hb ha] at hab,
  exact le_of_dvd ha.bot_lt hab,
end

@[simp] lemma factorization_div {d n : ℕ} (h : d ∣ n) :
  (n / d).factorization = n.factorization - d.factorization :=
begin
  rcases eq_or_ne d 0 with rfl | hd, { simp [zero_dvd_iff.mp h] },
  rcases eq_or_ne n 0 with rfl | hn, { simp },
  apply add_left_injective d.factorization,
  simp only,
  rw [tsub_add_cancel_of_le $ (nat.factorization_le_iff_dvd hd hn).mpr h,
      ←nat.factorization_mul (nat.div_pos (nat.le_of_dvd hn.bot_lt h) hd.bot_lt).ne' hd,
      nat.div_mul_cancel h],
end

lemma not_dvd_div_pow_factorization {n p : ℕ} (hp : prime p) (hn : n ≠ 0) :
  ¬p ∣ n / p ^ n.factorization p :=
begin
  rw [nat.prime.dvd_iff_one_le_factorization hp (div_pow_factorization_ne_zero p hn),
    nat.factorization_div (nat.pow_factorization_dvd n p)],
  simp [hp.factorization],
end

lemma coprime_of_div_pow_factorization {n p : ℕ} (hp : prime p) (hn : n ≠ 0) :
  coprime p (n / p ^ n.factorization p) :=
(or_iff_left (not_dvd_div_pow_factorization hp hn)).mp $ coprime_or_dvd_of_prime hp _

lemma dvd_iff_div_factorization_eq_tsub {d n : ℕ} (hd : d ≠ 0) (hdn : d ≤ n) :
  d ∣ n ↔ (n / d).factorization = n.factorization - d.factorization :=
begin
  refine ⟨factorization_div, _⟩,
  rcases eq_or_lt_of_le hdn with rfl | hd_lt_n, { simp },
  have h1 : n / d ≠ 0 := λ H, nat.lt_asymm hd_lt_n ((nat.div_eq_zero_iff hd.bot_lt).mp H),
  intros h,
  rw dvd_iff_le_div_mul n d,
  by_contra h2,
  cases (exists_factorization_lt_of_lt (mul_ne_zero h1 hd) (not_le.mp h2)) with p hp,
  rwa [factorization_mul h1 hd, add_apply, ←lt_tsub_iff_right, h, tsub_apply,
    lt_self_iff_false] at hp
end

lemma dvd_iff_prime_pow_dvd_dvd (n d : ℕ) :
  d ∣ n ↔ ∀ p k : ℕ, prime p → p ^ k ∣ d → p ^ k ∣ n :=
begin
  rcases eq_or_ne n 0 with rfl | hn, { simp },
  rcases eq_or_ne d 0 with rfl | hd,
  { simp only [zero_dvd_iff, hn, false_iff, not_forall],
    refine ⟨2, n, prime_two, ⟨dvd_zero _, _⟩⟩,
    apply mt (le_of_dvd hn.bot_lt) (not_le.mpr (lt_two_pow n)) },
  refine ⟨λ h p k _ hpkd, dvd_trans hpkd h, _⟩,
  rw [←factorization_le_iff_dvd hd hn, finsupp.le_def],
  intros h p,
  by_cases pp : prime p, swap, { simp [factorization_eq_zero_of_non_prime d pp] },
  rw ←pp.pow_dvd_iff_le_factorization hn,
  exact h p _ pp (pow_factorization_dvd _ _)
end

lemma prod_prime_factors_dvd (n : ℕ) : (∏ (p : ℕ) in n.factors.to_finset, p) ∣ n :=
begin
  by_cases hn : n = 0, { subst hn, simp },
  simpa [prod_factors hn] using multiset.to_finset_prod_dvd_prod (n.factors : multiset ℕ),
end

lemma factorization_gcd {a b : ℕ} (ha_pos : a ≠ 0) (hb_pos : b ≠ 0) :
  (gcd a b).factorization = a.factorization ⊓ b.factorization :=
begin
  let dfac := a.factorization ⊓ b.factorization,
  let d := dfac.prod pow,
  have dfac_prime : ∀ (p : ℕ), p ∈ dfac.support → prime p,
  { intros p hp,
    have : p ∈ a.factors ∧ p ∈ b.factors := by simpa using hp,
    exact prime_of_mem_factors this.1 },
  have h1 : d.factorization = dfac := prod_pow_factorization_eq_self dfac_prime,
  have hd_pos : d ≠ 0 := (factorization_equiv.inv_fun ⟨dfac, dfac_prime⟩).2.ne.symm,
  suffices : d = (gcd a b), { rwa ←this },
  apply gcd_greatest,
  { rw [←factorization_le_iff_dvd hd_pos ha_pos, h1], exact inf_le_left },
  { rw [←factorization_le_iff_dvd hd_pos hb_pos, h1], exact inf_le_right },
  { intros e hea heb,
    rcases decidable.eq_or_ne e 0 with rfl | he_pos,
    { simp only [zero_dvd_iff] at hea, contradiction, },
    have hea' := (factorization_le_iff_dvd he_pos ha_pos).mpr hea,
    have heb' := (factorization_le_iff_dvd he_pos hb_pos).mpr heb,
    simp [←factorization_le_iff_dvd he_pos hd_pos, h1, hea', heb'] },
end

/-! ### Factorization and coprimes -/

/-- For coprime `a` and `b`, the power of `p` in `a * b` is the sum of the powers in `a` and `b` -/
lemma factorization_mul_apply_of_coprime {p a b : ℕ} (hab : coprime a b)  :
  (a * b).factorization p = a.factorization p + b.factorization p :=
by simp only [←factors_count_eq, perm_iff_count.mp (perm_factors_mul_of_coprime hab), count_append]

/-- For coprime `a` and `b`, the power of `p` in `a * b` is the sum of the powers in `a` and `b` -/
lemma factorization_mul_of_coprime {a b : ℕ} (hab : coprime a b) :
  (a * b).factorization = a.factorization + b.factorization :=
begin
  ext q,
  simp only [finsupp.coe_add, add_apply, ←factors_count_eq, factorization_mul_apply_of_coprime hab],
end

/-- If `p` is a prime factor of `a` then the power of `p` in `a` is the same that in `a * b`,
for any `b` coprime to `a`. -/
lemma factorization_eq_of_coprime_left {p a b : ℕ} (hab : coprime a b) (hpa : p ∈ a.factors) :
  (a * b).factorization p = a.factorization p :=
begin
  rw [factorization_mul_apply_of_coprime hab, ←factors_count_eq, ←factors_count_eq],
  simpa only [count_eq_zero_of_not_mem (coprime_factors_disjoint hab hpa)],
end

/-- If `p` is a prime factor of `b` then the power of `p` in `b` is the same that in `a * b`,
for any `a` coprime to `b`. -/
lemma factorization_eq_of_coprime_right {p a b : ℕ} (hab : coprime a b) (hpb : p ∈ b.factors) :
  (a * b).factorization p = b.factorization p :=
by { rw mul_comm, exact factorization_eq_of_coprime_left (coprime_comm.mp hab) hpb }

/-- The prime factorizations of coprime `a` and `b` are disjoint -/
lemma factorization_disjoint_of_coprime {a b : ℕ} (hab : coprime a b) :
  disjoint a.factorization.support b.factorization.support :=
by simpa only [support_factorization]
  using disjoint_to_finset_iff_disjoint.mpr (coprime_factors_disjoint hab)

/-- For coprime `a` and `b` the prime factorization `a * b` is the union of those of `a` and `b` -/
lemma factorization_mul_support_of_coprime {a b : ℕ} (hab : coprime a b) :
  (a * b).factorization.support = a.factorization.support ∪ b.factorization.support :=
begin
  rw factorization_mul_of_coprime hab,
  exact support_add_eq (factorization_disjoint_of_coprime hab),
end

/-! ### Induction principles involving factorizations -/

/-- Given `P 0, P 1` and a way to extend `P a` to `P (p ^ n * a)` for prime `p` not dividing `a`,
we can define `P` for all natural numbers. -/
@[elab_as_eliminator]
def rec_on_prime_pow {P : ℕ → Sort*} (h0 : P 0) (h1 : P 1)
  (h : ∀ a p n : ℕ, p.prime → ¬ p ∣ a → 0 < n → P a → P (p ^ n * a)) : ∀ (a : ℕ), P a :=
λ a, nat.strong_rec_on a $ λ n,
  match n with
  | 0     := λ _, h0
  | 1     := λ _, h1
  | (k+2) := λ hk, begin
    let p := (k + 2).min_fac,
    have hp : prime p := min_fac_prime (succ_succ_ne_one k),
    -- the awkward `let` stuff here is because `factorization` is noncomputable (finsupp);
    -- we get around this by using the computable `factors.count`, and rewriting when we want
    -- to use the `factorization` API
    let t := (k+2).factors.count p,
    have ht : t = (k+2).factorization p := factors_count_eq,
    have hpt : p ^ t ∣ k + 2 := by { rw ht, exact pow_factorization_dvd _ _ },
    have htp : 0 < t :=
    by { rw ht, exact hp.factorization_pos_of_dvd (nat.succ_ne_zero _) (min_fac_dvd _) },
    convert h ((k + 2) / p ^ t) p t hp _ _ _,
    { rw nat.mul_div_cancel' hpt, },
    { rw [nat.dvd_div_iff hpt, ←pow_succ', ht],
      exact pow_succ_factorization_not_dvd (k + 1).succ_ne_zero hp },
    { exact htp },
    { apply hk _ (nat.div_lt_of_lt_mul _),
      simp [lt_mul_iff_one_lt_left nat.succ_pos', one_lt_pow_iff htp.ne, hp.one_lt] },
    end
  end

/-- Given `P 0`, `P 1`, and `P (p ^ n)` for positive prime powers, and a way to extend `P a` and
`P b` to `P (a * b)` when `a, b` are positive coprime, we can define `P` for all natural numbers. -/
@[elab_as_eliminator]
def rec_on_pos_prime_pos_coprime {P : ℕ → Sort*} (hp : ∀ p n : ℕ, prime p → 0 < n → P (p ^ n))
  (h0 : P 0) (h1 : P 1) (h : ∀ a b, 1 < a → 1 < b → coprime a b → P a → P b → P (a * b)) :
  ∀ a, P a :=
rec_on_prime_pow h0 h1 $
begin
  intros a p n hp' hpa hn hPa,
  by_cases ha1 : a = 1,
  { rw [ha1, mul_one],
    exact hp p n hp' hn },
  refine h (p^n) a ((hp'.one_lt).trans_le (le_self_pow (prime.one_lt hp').le (succ_le_iff.mpr hn)))
    _ _ (hp _ _ hp' hn) hPa,
  { contrapose! hpa,
    simp [lt_one_iff.1 (lt_of_le_of_ne hpa ha1)] },
  simpa [hn, prime.coprime_iff_not_dvd hp'],
end

/-- Given `P 0`, `P (p ^ n)` for all prime powers, and a way to extend `P a` and `P b` to
`P (a * b)` when `a, b` are positive coprime, we can define `P` for all natural numbers. -/
@[elab_as_eliminator]
def rec_on_prime_coprime {P : ℕ → Sort*} (h0 : P 0) (hp : ∀ p n : ℕ, prime p → P (p ^ n))
  (h : ∀ a b, 1 < a → 1 < b → coprime a b → P a → P b → P (a * b)) : ∀ a, P a :=
rec_on_pos_prime_pos_coprime (λ p n h _, hp p n h) h0 (hp 2 0 prime_two) h

/-- Given `P 0`, `P 1`, `P p` for all primes, and a way to extend `P a` and `P b` to
`P (a * b)`, we can define `P` for all natural numbers. -/
@[elab_as_eliminator]
def rec_on_mul {P : ℕ → Sort*} (h0 : P 0) (h1 : P 1)
  (hp : ∀ p, prime p → P p) (h : ∀ a b, P a → P b → P (a * b)) : ∀ a, P a :=
let hp : ∀ p n : ℕ, prime p → P (p ^ n) :=
  λ p n hp', match n with
  | 0     := h1
  | (n+1) := by exact h _ _ (hp p hp') (_match _)
  end in
rec_on_prime_coprime h0 hp $ λ a b _ _ _, h a b

/-- For any multiplicative function `f` with `f 1 = 1` and any `n ≠ 0`,
we can evaluate `f n` by evaluating `f` at `p ^ k` over the factorization of `n` -/
lemma multiplicative_factorization {β : Type*} [comm_monoid β] (f : ℕ → β)
  (h_mult : ∀ x y : ℕ, coprime x y → f (x * y) = f x * f y) (hf : f 1 = 1) :
  ∀ {n : ℕ}, n ≠ 0 → f n = n.factorization.prod (λ p k, f (p ^ k)) :=
begin
  apply' nat.rec_on_pos_prime_pos_coprime,
  { intros p k hp hk hpk, simp [prime.factorization_pow hp, finsupp.prod_single_index _, hf] },
  { simp },
  { rintros -, rw [factorization_one, hf], simp },
  { intros a b _ _ hab ha hb hab_pos,
    rw [h_mult a b hab, ha (left_ne_zero_of_mul hab_pos), hb (right_ne_zero_of_mul hab_pos),
        factorization_mul_of_coprime hab, ←prod_add_index_of_disjoint],
    convert (factorization_disjoint_of_coprime hab) },
end

/-- For any multiplicative function `f` with `f 1 = 1` and `f 0 = 1`,
we can evaluate `f n` by evaluating `f` at `p ^ k` over the factorization of `n` -/
lemma multiplicative_factorization' {β : Type*} [comm_monoid β] (f : ℕ → β)
  (h_mult : ∀ x y : ℕ, coprime x y → f (x * y) = f x * f y) (hf0 : f 0 = 1) (hf1 : f 1 = 1) :
  ∀ {n : ℕ}, f n = n.factorization.prod (λ p k, f (p ^ k)) :=
begin
  apply' nat.rec_on_pos_prime_pos_coprime,
  { intros p k hp hk, simp only [hp.factorization_pow], rw prod_single_index _, simp [hf1] },
  { simp [hf0] },
  { rw [factorization_one, hf1], simp },
  { intros a b _ _ hab ha hb,
    rw [h_mult a b hab, ha, hb, factorization_mul_of_coprime hab, ←prod_add_index_of_disjoint],
    convert (factorization_disjoint_of_coprime hab) },
end

end nat


/-
This file is part of the second project based on Ideals in Lean for the Formalising Mathematics
course at Imperial College London.

Motivation: Ideals form a Complete Lattice.

Author: Additi Pandey
CID : 02119403
-/

import tactic

import data.set_like.basic

/-!
Defining Ideals in Lean and representing them in the form of a Complete Lattice.
We will start off by defining the ideals in Lean in the form of a structure called `my_ideal`.

## Give the definition of an ideal of a ring.
A subring `A` of a ring `R` is called a (two-sided) ideal of `R` if for every `r ∈ R` and every
`a ∈ A` both ` r * a` and `a * r` are in `A`.

We will deal with ideals of a commutative ring, which will be denoted by `R`.

To define the ideal of a commutative ring `R`, we will apply the Ideal Test.
The ideal test says an ideal `A` is a nonempty subset (always has the 0 element) of a ring `R` if
1. `r * a` in `A` whenever `a ∈ A` and `r ∈ R`. (The ring `R` is commuatative.)
2. `a-b ∈ A` whenever `a, b ∈ A`.

## Implementation notes
Ideal inclusion is denoted by `≤` rather than by `⊆`.
-/

-- Throughout this file, `R` will denote a commutative ring, whose ideals will be talked about.
variable {R: Type*}

/-- Using the ideal test (mentioned above), the structure `my_ideal` is defined as: -/
structure my_ideal (R : Type*) [comm_ring R] :=
(carrier : set R)
(zero_mem' : (0 : R) ∈ carrier) -- denotes the property of a non-empty subset
(mul_mem' {a b} : b ∈ carrier → a * b ∈ carrier) -- denotes property 1.
(sub_mem' {a b} : a ∈ carrier → b ∈ carrier → a - b ∈ carrier) -- denotes property 2.

-- Moving to the section of `comm_ring` so as to limit the scope of the variables declared.
section comm_ring

namespace my_ideal

/-
 Declaring variables such that, throughout the code:
`R` is `comm_ring`
`S, I , J` will denote `my_ideal R`
`a,b` will denote `R`
-/
variables [comm_ring R] (S : my_ideal R) {a b : R} {I J : my_ideal R}

/-- Declaring the instance `set_like` from `my_ideal R` to the set `R` -/
instance : set_like (my_ideal R) R :=
begin
  constructor,
  swap,
  exact my_ideal.carrier,
  intros p q h,
  cases p,
  cases q,
  congr',
end

-- Checking if inclusion is defined by `set_like`:
#check I ≤ J

/-
Note: `set_like` defines an instance called `partial_order A` which is followed by a lemma
which talks about the inclusion of two variables that denote `A`, given by:

lemma le_def {S T : A} : S ≤ T ↔ ∀ ⦃x : B⦄, x ∈ S → x ∈ T := iff.rfl

Hence, the command `#check I ≤ J` right after the declaration of the `set_like` instance.

However, I have tried showing inclusion more explicitly below:
-/

/--
The lemma given by `mem_carrier`, `mem_mk` and `coe_set_mk`, do not come into use, however, have
still been included in the code as they give some important results.
-/
-- To show: `x ∈ I.carrier` iff `x ∈ I`.
lemma mem_carrier {I : my_ideal R} {x : R} : x ∈ I.carrier ↔ x ∈ I := iff.rfl

-- To show: If `x` fulfills all axioms in `my_ideal R` then `x` is in `set R`.
lemma mem_mk {s : set R} {x : R} (h_one) (h_mul) (h_add) : x ∈ mk s h_one h_mul h_add ↔ x ∈ s := iff.rfl

-- Generalising the previous lemma:
lemma coe_set_mk {s : set R} (h_one) (h_mul) (h_add): (mk s h_one h_mul h_add : set R) = s := rfl

-- Finally, proving inclusion in ideals:
/-- The lemma stating the inclusion of two ideals: -/
lemma mk_le_mk { I J : set R} (h_one) (h_mul) (h_sub) (h_one') (h_mul') (h_sub') :
  mk I h_one h_mul h_sub ≤ mk J h_one' h_mul' h_sub'↔ I ⊆ J := iff.rfl

/-- Two ideals are equal if they have the same elements. -/
theorem ext {I J : my_ideal R}
(h : ∀ x, x ∈ I ↔ x ∈ J) : I = J := set_like.ext h

-- To fix definitional equalities:
/-- Copying an ideal replacing `carrier` with a set that is equal to it: -/
def copy (S : my_ideal R) (s : set R) (hs : s = S) : my_ideal R :=
{ carrier := s,
  zero_mem' := hs.symm ▸ S.zero_mem',
  mul_mem' := hs.symm ▸ S.mul_mem',
  sub_mem' := hs.symm ▸ S.sub_mem',
  }

@[simp] lemma coe_copy (p : my_ideal R) (s : set R) (hs : s = ↑p) :
  (p.copy s hs : set R) = s := rfl

lemma copy_eq (p : my_ideal R) (s : set R) (hs : s = ↑p) : p.copy s hs = p :=
set_like.coe_injective hs

variable (S)

/-- Every ideal has a zero element. -/
@[simp]theorem zero_mem : (0 : R) ∈ S := S.zero_mem'

/-- Ideals are closed under multiplication. -/
@[simp]theorem mul_mem {x y : R} : y ∈ S → x * y ∈ S := mul_mem' S

/-- Ideals have additive inverses in them. -/
theorem neg_one_mul_mem : b ∈ S → (-1 : R) * b ∈ S :=
begin
  apply mul_mem,
end

/-- Ideals are subsets such that they fulfill  `sub_mem` as they are also additive subgroups. -/
@[simp]theorem sub_mem {x y : R} : x ∈ S → y ∈ S → x - y ∈ S := sub_mem' S

/-
Although, the lemma included in these comments is not used anywhere in the file, it provided me an
insight as to how to go about proving `add_mem` by using only the available axioms defined in the
structure `my_ideal R`.

theorem neg_one_mul (x ∈ S) : (-1 : R) * x = -x :=
begin
  rw neg_one_mul,
end
-/

/-- Proving that ideals contain additive inverse of their elements with the help of
the theorem `neg_one_mul_mem`. -/
@[simp] lemma neg_mem : a ∈ S → -a ∈ S :=
begin
  intro h,
  rw neg_eq_neg_one_mul,
  apply neg_one_mul_mem,
  apply h,
end

/-- Ideals are closed under addition. -/
@[simp]theorem add_mem {x y : R} : x ∈ S → y ∈ S → x + y ∈ S :=
begin
  intros h p,
  have hQ: -y ∈ S,
  apply neg_mem,
  exact p,
  have hT : x - (-y) ∈ S,
  apply sub_mem,
  exact h,
  exact hQ,
  finish,
end

/-- Ideals are closed under right multiplication , that is, `a * r ∈ S`
if `a ∈ S` and `r ∈ R` -/
@[simp]theorem mul_mem_right {x y : R} : x ∈ S → x * y ∈ S :=
begin
  intro h,
  rw mul_comm,
  apply mul_mem,
  exact h,
end

/-- The ideal `(0)` of the commutative ring `R`. -/
instance : has_bot (my_ideal R) :=
⟨{ carrier := {0},
   zero_mem' := set.mem_singleton 0,
   mul_mem' :=
   begin
     intros a b ha,
    simp only[set.mem_singleton_iff] at *,
    rw ha,
    apply mul_zero,
   end,
   sub_mem' :=
   begin
     intros a b ha hb,
     simp only[set.mem_singleton_iff] at *,
     rw ha,
     rw hb,
     simp,
   end
}⟩

/-- The ideal `R` of the commutative ring `R`. -/
instance : has_top (my_ideal R) :=
⟨{carrier := set.univ,
  zero_mem' := set.mem_univ 0,
  mul_mem' :=
  begin
    intros a b ha,
    simp,
  end,
  sub_mem' :=
  begin
    intros a b ha hb,
    simp,
  end
}⟩

instance : inhabited (my_ideal R) := ⟨⊥⟩

/-- Since the bottom element is the (0) ideal, therefore, any element in it will be 0. -/
@[simp]lemma mem_bot {x : R} : x ∈ (⊥ : my_ideal R) ↔ x = 0 := set.mem_singleton_iff

/-- Since the top element is the whole commutative ring `R`, therefore, an element
belonging to the top element belongs to the commutative ring `R`. -/
@[simp]lemma mem_top (x : R) : x ∈ (⊤ : my_ideal R) := set.mem_univ x

/-- The inf of two ideals is their intersection. -/
instance : has_inf (my_ideal R) :=
⟨λ I J,
  { carrier := I ∩ J,
    zero_mem' :=
    begin
      fconstructor,
      apply zero_mem,
      apply zero_mem,
    end,
    mul_mem' :=
    begin
      intros a b c,
      fconstructor,
      simp at c,
      cases c,
      simp,
      apply mul_mem,
      exact c_left,
      simp at c,
      cases c,
      simp,
      apply mul_mem,
      exact c_right,
    end,
    sub_mem' :=
    begin
      intros a b c d,
      simp at c,
      simp at d,
      cases c,
      cases d,
      simp,
      split,
      apply sub_mem,
      exact c_left,
      exact d_left,
      apply sub_mem,
      exact c_right,
      exact d_right,
    end
  }
⟩

/-- The infimum of possibly an infinitely many ideals is also their intersection.-/
instance : has_Inf (my_ideal R) :=
⟨λ S,
{ carrier := ⋂ t ∈ S, ↑t,
  zero_mem' := set.mem_bInter $ λ i h, i.zero_mem,
  mul_mem' := λ x y hy, set.mem_bInter $ λ i h,
    i.mul_mem (by apply set.mem_bInter_iff.1 hy i h),
  sub_mem' := λ x y hx hy, set.mem_bInter $ λ i h,
    i.sub_mem (by apply set.mem_bInter_iff.1 hx i h) (by apply set.mem_bInter_iff.1 hy i h) }⟩

/-
The union of two ideals is not necessarily an ideal. For example, the ideals `2ℤ` and `3ℤ` in
the ring `ℤ`, have their union as `2ℤ ∪ 3ℤ` but this is not an ideal as it does not contain
the element `2+3=5`, hence, fails to be a subring.

Hence, the union of two ideals is a subring ↔ one ideal ≤ another ideal.

So, one can certainly say that the union of two ideals cannot be their supremum. For any ideal
to be the union, it must definitely contain the two ideals as well as be closed under addition.

Therefore, the ideal `I + J` fulfills the two criterion and hence, "can" be the supremum.

The file will later investigate if the claim that my_ideal `I + J` is same as the supremum of ideals
defined by Lean under the name `has_sup`, represented by `I ⊔ J`.
-/

/-- Defining `has_add` to be the sum of two ideals: -/
instance : has_add (my_ideal R) :=
⟨λ I J, { carrier   := {x | ∃ (i ∈ I) (j ∈ J), x = i + j},
  zero_mem':=
  begin
    simp at *,
    fconstructor,
    exact ring.zero,
    fconstructor,
    exact zero_mem I,
    fconstructor,
    exact ring.zero,
    fconstructor,
    exact zero_mem J,
    exact self_eq_add_left.mpr rfl,
  end,
  mul_mem' :=
  begin
    intros a b ha,
    cases ha with m hm,
    dsimp,
    use (a*m),
    split,
    change ∃ (H : m ∈ I) (k : R) (H : k ∈ J), b = m + k at hm,
    apply mul_mem I,
    finish,
    cases hm with t ht,
    cases ht with p hp,
    use (a*p),
    split,
    apply mul_mem J,
    finish,
    simp at *,
    cases hp,
    rw hp_right,
    apply mul_add,
  end,
  sub_mem' :=
  begin
    intros a b ha hb,
    cases ha with m hm,
    cases hb with n hn,
    cases hm with p hp,
    cases hn with q hq,
    cases hp with s hs, cases hq with t ht,
    dsimp,
    cases hs with x hx,
    cases ht with y hy,
    use (m-n),
    split,
    finish,
    use (s-t),
    split,
    finish,
    rw hx,
    rw hy,
    exact add_sub_comm m s n t,
  end }⟩

-- Advancing towards giving ideals the structure of a complete lattice:

/-
Having defined inclusion - also directly implied by the instance `set_like`, top ⊤ element and
the bottom ⊥ element, the meet of ideals given by `has_inf` and `has_Inf`, which generates the
join of ideals `has_sup` and `has_Sup` by taking the infimum of all the ideals which contain the
two ideals `I, J`, we now give `my_ideal R` the structure of a compelete lattice.
-/

/-- The instance `complete_lattice` defines the complete lattice formed by ideals of the form
`my_ideal R`. -/
instance : complete_lattice (my_ideal R) :=
{ le           := (≤),
  lt           := (<),
  bot          := (⊥),
  bot_le       := λ S x hx, (mem_bot.1 hx).symm ▸ S.zero_mem,
  top          := (⊤),
  le_top       := λ S x hx, mem_top x,
  inf          := (⊓),
  Inf          := has_Inf.Inf,
  le_inf       := λ a b c ha hb x hx, ⟨ha hx, hb hx⟩,
  inf_le_left  := λ a b x, and.left,
  inf_le_right := λ a b x, and.right,
  .. complete_lattice_of_Inf (my_ideal R) $ λ s,
    is_glb.of_image (λ S T,
      show (S : set R) ≤ T ↔ S ≤ T, from set_like.coe_subset_coe) is_glb_binfi }

/-
The `complete_lattice` defines a complete lattice formed by ideals without explicitly
defining `has_sup` or `has_Sup` as, it generates them by using `has_inf` and `has_Inf`.

However, I claimed that `has_add` is the supremum of ideals of the form `my_ideal`, and hence,
I will try to prove that is it same as the `has_sup` defined for ideals by Lean.
-/

/-- Introducing a lemma to provide a more defined structure to the elements in `I + J`. -/
@[simp] lemma has_add_mem (x : R) : x ∈ I + J ↔ ∃ (i ∈ I) (j ∈ J), x = i + j :=
begin
  split,
  tauto,
  itauto,
end

-- Proof that the claim is true :

/-- Proving that `has_add` represented by `I + J` is same as `has_sup` defined by Lean and
represented by `I ⊔ J`. -/
lemma has_add_eq_has_sup : I + J = I ⊔ J :=
begin
  apply ext,
  intro x,
  split,
  intro a,
  simp at *,
  cases a with p hp,
  cases hp with q hq,
  cases hq with r hr,
  cases hr with s hs,
  rw hs,
  have hv1 : J ≤ I ⊔ J,
  finish,
  have hv : r ∈ I ⊔ J,
  tauto,
  have hu1 : I ≤ I ⊔ J,
  finish,
  have hu : p ∈ I ⊔ J,
  tauto,
  finish,
  suffices : I ⊔ J ≤ I + J,
  tauto,
  have h1: I ≤ I + J,
  intros a b,
  simp at *,
  use a,
  split,
  exact b,
  use 0,
  split,
  apply zero_mem J,
  rw add_zero,
  have h2: J ≤ I + J,
  intros a b,
  simp,
  use 0,
  split,
  apply zero_mem I,
  use a,
  split,
  exact b,
  rw zero_add,
  apply sup_le h1 h2,
end

/-
This part of code although not required, is still present in the file, as it was used
to confirm if certain structures have been defined automatically by `complete_lattice`

example : has_le (my_ideal R) := by apply_instance

example : partial_order (my_ideal R) := by apply_instance

example : lattice (my_ideal R) := by apply_instance

example : complete_lattice (my_ideal R) := by apply_instance

example : has_lt (my_ideal R) := by apply_instance

example :  has_sup (my_ideal R) := by apply_instance

example :  semilattice_sup (my_ideal R) := by apply_instance
-/

/-
Since the motivation behind the project- to give ideals a structure of complete lattice,
has been fulfilled, the namespace `my_ideal` and section `comm_ring` can be closed.
-/

end my_ideal
end comm_ring

#lint