/- ***********************-/
/- *** ∃ Introduction *** -/
/-************************-/

/-
An existentially quantified
proposition asserts that there
exists *some* value of some type
that makes a given predicate
true. Here's an example.
-/

def anExistsProp :=
    ∃ m , m + m = 8

/- This proposition asserts that
there is some value m (inferred
to be of type ℕ) that makes the
predicate, m + m = 8, true.

Basic algebra tells us that there
is such a value, namely 4, so this
existentially quantified proposition
can be proved and is true.

The key thing to remember is that
a proof of such an existentially
quantified proposition is a pair.

The first element is a value that
makes the sub-proposition true. We
call such a value a "witness". In
the example, the witness is 4.

The second element in a proof
is a proof of that the proposition
obtained by substituting the witness
in for the value of the variable in
the predicate is true. Here, the
proof must be a proof of 4 + 4 = 8.

A proof of ∃ m , m + m = 8, is
thus the pair, ⟨ 4, eq.refl 8 ⟩. Here we use
special angle brackets, a notation that
Lean recognizes for writing proofs of
existentially quantified propositions.
-/

/-
Here are a couple more examples.
Note, as with all propositions,
these existential propositions do
not have to be true.
-/

def anotherExistsProp :=
  exists m, m > 10

def yetAnotherExistsProp :=
  exists m, m - m = 3

/-
Consider a familiar expression:

You can fool all of the people some of the time…
  ∀ p ∈ People, ∃ t ∈ time, fool(p, t)
    — everybody can be fooled at one time or another
  ∃ t ∈ time, ∀ p ∈ People, fool(p, t)
    — there exists a time when all of the people can be fooled simultaneously
  ∃ t ∈ time, ∀ p ∈ People, fool(p, t) →
    ∀ p ∈ People, ∃ t ∈ time, fool(p, t)
…and some of the people all of the time.
  ∃ p ∈ People, ∀ t ∈ time, fool(p, t)
    — there exists somebody who can be fooled all of the time
  ∀ t ∈ time, ∃ p ∈ People, fool(p, t)
    — at any given moment, there exists somebody who can be fooled
  ∃ p ∈ People, ∀ t ∈ time, fool(p, t) →
    ∀ t ∈ time, ∃ p ∈ People, fool(p, t)
-/

/-
More generally the introduction
rule for ∃ is as follows:

{ T : Type } { p: T → Prop } (w : T) (e : p w)
---------------------------------------------- exists.intr
                    ∃ a : T, p a
-/

#print exists.intro

/-
Here's code that illustrates the use of
-/

def existsIntro
(T: Type)           -- arguments
(pred: T → Prop)
(w : T)
(e : pred w)
:
exists w, pred w    -- return type
:=
exists.intro w e    -- direct use of exists. intro

#check existsIntro

/-
Abstract example
-/
variable T : Type
variable witness : T
variable predicate : T → Prop
#check predicate witness
variable proof : predicate witness

-- direct use of exists.intro
def pfOfExists : ∃ m, predicate m :=
    exists.intro witness proof

-- shorthand
def pfOfExists' : ∃ m, predicate m :=
    ⟨ witness, proof ⟩

-- a script in which we build the proof interactively
def fourAgain : exists m, m + m = 8 :=
begin
    apply exists.intro 4,
    exact rfl,
end

#reduce fourAgain

/-
Concrete example
-/

def isEven (n : nat) : Prop :=
    exists m : nat, m + m = n

#check isEven

/-
A bad definition of isEven (last time).
-/

def isEvenBadDef (n : nat) : Prop :=
    exists m : nat, n / m = 2

example : isEvenBadDef 7 :=
    ⟨ 3.5, rfl ⟩

-- OOPS "/"" is natural number division
#reduce (7 / 2 : nat)

#reduce isEven 8
--
def eightEven := isEven 8

#check eightEven
#reduce eightEven

-- exact proof term using exists.intro
theorem even8 : eightEven :=
    exists.intro 4 rfl

-- syntactic sugar
theorem even8' : eightEven :=
    ⟨ 4, rfl ⟩

-- as a tactic script
-- unfold expands a definition
theorem even8'' : isEven 8 :=
begin
unfold isEven,      -- not necessary
apply exists.intro 4,
exact rfl
end

/-
EXERCISE: Construct a proof, isNonZ,
of the proposition that there exists
a natural number n such that 0 ≠ n.
-/

-- Possible Answers --

theorem isNonZ : exists n : nat, 0 ≠ n :=
exists.intro 1 (λ pf : (0 = 1),
nat.no_confusion pf)
-- second arg is a pf of 0=1→false

theorem isNonZ' : exists n : nat, 0 ≠ n :=
begin
  apply exists.intro 1,
  assume contra: 0=1,
  exact nat.no_confusion contra
end

theorem isNonZ'' : exists n : nat, 0 ≠ n :=
begin
  have pf0isnt1: (0 ≠ 2),
    apply nat.no_confusion,

  exact ⟨ 2, pf0isnt1 ⟩
end

theorem isNonZ''' : exists n : nat, 0 ≠ n :=
  ⟨ 3, dec_trivial ⟩

/- **********************-/
/- *** ∃ Elimination *** -/
/-***********************-/

/-
Suppose you want to show: ∃ x, P x → Q.

If one assumes that ∃ x, P x, then one
can assume there is some specific value,
w, such that P w is true. If one can then
show, without making additional assumptions
about w, that some conclusion, Q (that does
not depend on w), follows, then one has shown
that Q follows from the mere existence of a
w with property P, and thus from ∃ x, P x.

The formal rule is a little bit involved.
Here it is as an inference rule. We explain
each piece below.

∀ {Q : Prop}, ∀ {T : Type }, ∀ { P : T → Prop},
pfEx: (∃ x : T, P x), pfP2Q: ∀ w : T, P w → Q
----------------------------------------------
               Q

Ignore the implicit parameters for a moment,
and focus on pfEx and pfP2Q. This rule says
that (1) if there is some object, x that has
property P, and (2) if whenever *any* object
has this property, Q follows, then Q follows.

This is the elimination rule for ∃. It
lets you draw a conclusion, Q, from the
premise that ∃ x, P x (there is an x with
property P) and from a proof that if any
x has this property (and we just assumed
there is one) then there is a proof of Q.

The following function shows all of the
pieces needed to use exists.elim and how
to use it. Note that the conclusion, Q,
the type of elements involved, T, and
the property, P, are given implicitly,
as they can be inferred from the ∃ and
from the proof that Q follows from any
such value.
-/

def existsElim
    { Q : Prop }
    { T : Type }
    { P : T → Prop }
    ( ex : exists x, P x)
    ( pw2q : ∀ w : T, P w → Q)
    : Q
    :=
        exists.elim ex pw2q

/-
EXAMPLE. Let's prove that if there
is a value of some type that has two
properties, P and Q, then it has one
of those properties.

-/

-- assume pred1 and pred2 are properties of nats
variable pred1 : ℕ → Prop
variable pred2 : ℕ → Prop

/-
∀ {Q : Prop}, ∀ {T : Type }, ∀ { P : T → Prop},
pfEx: (∃ x : T, P x), pfP2Q: ∀ w : T, P w → Q
----------------------------------------------
               Q
-/

theorem forgetAProperty :
  (exists n, pred1 n ∧ pred2 n) → (exists n, pred1 n) :=
-- here "Q", the conclusion, is (exists n, pred1 n)
begin
  assume ex,
  show ∃ (n : ℕ), pred1 n,
  from
    begin
      apply exists.elim ex, -- give one arg, build  other
      assume w Pw,          -- assume w and proof of P w
      show ∃ (n : ℕ), pred1 n,
      from exists.intro w Pw.left,
    end,
end

/-
We'll need to understand the following fact
to fully understand the proof of the theorem
that follows. This fact says that if "a" is a
value (here of type ℕ) and pred is a predicate
on values (or type ℕ), and if you further have
a function that takes a value, x, and returns
pred x, then applying that function to a value
a returns pred a. Just keep this in mind as we
work through the follow-on proof.
-/

example:
  ∀ (a: ℕ), ∀ (pred: ℕ → Prop),
    (λ (x: ℕ), pred x) a = (pred a) :=
begin
  assume a pred,
  apply rfl
end

/-
We now define two simple predicates,
gt1 (λ x, x > 1) and pred4 (λ x, x < 4),
and two more complex predicates, P and Q.
P is the predicate that asserts that both
gt1 and pred4 are true of some x, and
Q is the predicate that asserts that
there is some x that satisfies gt1.

The reason for this "set up" is so that
we can state the main theorem that follows
in a way that makes cler the relevance
of the elimination rule for exists. We
want to prove Q from ∃(x: ℕ), P x.
-/

def gt1: ℕ → Prop := λ (x: ℕ), x > 1
def lt4: ℕ → Prop := λ (x: ℕ), x < 4
abbreviation P : ℕ → Prop := λ (x: ℕ), gt1 x ∧ lt4 x
abbreviation Q : Prop := ∃ (x: ℕ), gt1 x

#check P 2

theorem forgetAProperty':
  (∃(x: ℕ), P x) → Q :=
begin
  assume pf_existsP,
  show Q,  -- document goal for readability
  from
    begin
      apply exists.elim pf_existsP, -- give one arg, build  other
      assume w pf_Pw,               -- assume w and proof of P w
      show Q,
      apply exists.intro w,
      exact pf_Pw.left,
    end,
end

/-
*** EXERCISE:

Prove:
Assuming n is a natural number and P and S are
properties of natural numbers, prove that
(∃ n, P n ∧ S n) → (∃ n, S n ∧ P n).
-/

-- Answer

theorem reverseProperty : ∀ (P S : ℕ → Prop),
  (exists n, P n ∧ S n) → (exists n, S n ∧ P n) :=
  -- here Q, the conclusion, is (exists n, S n ∧ P n)
begin
  assume P S,
  assume ex,
  show ∃ (n : ℕ), S n ∧ P n,
  from
    begin
      apply exists.elim ex, -- give one arg, build other
      assume w Pw,          -- assume w and proof of P w
      show ∃ (n : ℕ), S n ∧ P n,
      -- here's some new notation for and.intro
      from ⟨ w, ⟨ Pw.right, Pw.left ⟩ ⟩
    end,
end

/-
The difficulty of proving propositions in
predicate logic is often related to the
nesting of quantifiers. Here's a little
example illustrating such nesting.

We formalize and prove this claim: if
there is a person who can be fooled at
any time, then at any time someone can
be fooled.

Here's a logical rendition of this idea:
∃ p ∈ Person,   ∀ t ∈ Time,     canFool(p, t) →
∀ t ∈ Time,     ∃ p ∈ Person,   canFool(p, t).

Note the different nestings of the quantifiers.

Let's prove it.
-/

theorem existsforall_impl_forallexists:
∀ (Time Person: Type),
∀ (canFool: Time → Person → Prop),
    (∃ p: Person, ∀ t: Time, canFool t p) →
        (∀ t: Time, ∃ p: Person, canFool t p)
:=
begin
  assume Time Person,
  assume canFool,
  assume someoneCanBeFooledAllTheTime,
  show ∀ (t : Time), ∃ (p : Person), canFool t p, from
  begin
    assume aTime,
    show ∃ (p : Person), canFool aTime p, from
        begin
        apply exists.elim someoneCanBeFooledAllTheTime,
        assume somePerson,
        assume canFoolThatPersonAnytime,
        have canFoolThatPersonSometime :=
            canFoolThatPersonAnytime aTime,
        exact exists.intro somePerson canFoolThatPersonSometime,
        --exact ⟨ somePerson, canFoolThatPersonSometime ⟩,
        end,
  end,
end

/-
The same proposition and proof using neutral identifiers.
-/
theorem existsforall_impl_forallexists':
∀ (T P: Type),
∀ (rel: T → P → Prop),
    (∃ p: P, ∀ t: T, rel t p) →
        (∀ t: T, ∃ p: P, rel t p)
:=
begin
  assume T P,
  assume rel,
  assume somePrelAllT,
  assume someT,
  apply exists.elim somePrelAllT,
  assume someP,
  assume thatPrelAllT,
  have thatTrelThatP :=
    thatPrelAllT someT,
  exact ⟨ someP, thatTrelThatP ⟩,
end

/-
Discussion: Is the converse proposition true?
-/

/-
Negating Existential and Universal Quantifiers

What happens when you negate an existential
quantifier? What does this mean:
¬(∃ t ∈ time, fool(me, t)) -
  there does not exist a time when you can fool me
∀ t ∈ time, ¬fool(me, t) -
  at any time, you will not fool me
Are these equivalent?

How about this:
¬(∀ t ∈ time, fool(me, t)) -
  you cannot fool me all of the time
∃ t ∈ time, ¬fool(me, t) -
  there exists a time when you cannot fool me
Are these equivalent?
-/

theorem not_exists_t_iff_always_not_t:
  ∀ (T: Type) (pred: (T → Prop)),
    (¬(∃(t: T), pred(t))) ↔
      ∀(t: T), ¬pred(t) :=
begin
  assume T pred,
  apply iff.intro,

  -- forward
  show (¬∃ (t : T), pred t) → ∀ (t : T), ¬pred t, from
    begin
        assume pf_not_exists_t,
        show ∀ (t : T), ¬pred t, from
        begin
            assume t,
            assume pred_t,
            have pf_exists_t := exists.intro t pred_t,
            exact (pf_not_exists_t pf_exists_t),
            -- contradiction,
        end,
    end,

    -- reverse
  show (∀ (t : T), ¬pred t) → (¬∃ (t : T), pred t), from
    begin
        assume all_not_pred,
        show ¬∃ (t : T), pred t, from
            begin
            assume ex_t_pred,
            show false, from
                begin
                apply exists.elim ex_t_pred,
                assume t,
                assume pred_t,
                have not_pred_t := all_not_pred t,
                --exact pf_not_pred_t pf_pred_t
                contradiction,
                end,
            end,
    end,
end

open classical
-- em is axiom of the excluded middle

theorem not_all_t_iff_exists_not_t:
  ∀ (T: Type) (pred: (T → Prop)),
    (¬(∀ t: T, pred(t))) ↔
      ∃ t: T, ¬pred(t) :=
begin
  intros,
  apply iff.intro,
    assume pf_not_all_t,
    cases (em (∃ (t : T), ¬pred t)) with pf_exists_not_t pf_not_exists_not_t,
      exact pf_exists_not_t,

      have contra_double_neg: ∀(t: T), ¬¬pred t,
        assume t,
        assume pf_n_pred_t,
        have pf_exists_t := exists.intro t pf_n_pred_t,
        exact (pf_not_exists_not_t pf_exists_t),

      have contra: ∀(t: T), pred t,
        assume t,
        have pf_nn_pred_t := (contra_double_neg t),
        cases em (pred t) with pf_pred_t pf_n_pred_t,
          assumption,

          exact false.elim (pf_nn_pred_t pf_n_pred_t),

      exact false.elim (pf_not_all_t contra),

    assume pf_exists_not_t,
    apply exists.elim pf_exists_not_t,
    assume w not_pred_w,
    assume pf_all_t,
    show false, from
      begin
        have pred_w := pf_all_t w,
        exact not_pred_w pred_w,
      end,
end

/-
Satisfiability

Satisfiability is about finding values for
sub-propositions that make a larger proposition
true. We typically like to formulate these
questions in something known as conjunctive
normal form (or CNF). In CNF we have a series
of disjunctions, e.g., x1 ∨ x2 ∨ ¬x3 ∨ x4.
These disjunctions are then combined into
the larger proposition using conjunctions, e.g.:
(x1 ∨ x2 ∨ ¬x3 ∨ x4) ∧ (¬x1 ∨ x2 ∨ ¬x3).

For example, do there exist values for P and Q
such that:
(P ∨ Q) ∧ (¬P ∨ ¬Q) is true?
-/

example: ∃(P Q: Prop), (P ∨ Q) ∧ (¬P ∨ ¬Q) :=
begin
  apply exists.intro true,
  apply exists.intro false,
  apply and.intro,
    -- prove true or false
    apply or.inl true.intro,

    -- prove not true or not false,
    apply or.inr false.elim,
end

/-
Exercise:
Do there exist values for P and Q such that:
(P ∨ Q) ∧ (¬P ∨ ¬Q) ∧ (¬P or Q) is true?
If so, prove it.
-/

-- Answer:

example: ∃(P Q: Prop), (P ∨ Q) ∧ (¬P ∨ ¬Q) ∧ (¬P ∨ Q) :=
begin
  apply exists.intro false,
  apply exists.intro true,
  apply and.intro,
    -- prove true or false
    apply or.inr true.intro,

    apply and.intro,
      -- prove not true or not false
      apply or.inl false.elim,

      -- prove not false or true
      apply or.inr true.intro
end

/-
Exercise:
Do there exist values for P and Q such that:
(P ∨ Q) ∧ (¬P ∨ ¬Q) ∧ (¬P ∨ Q) ∧ (¬Q) is true?
If so, prove it.
-/

-- Answer:

-- No such values exist

/-
3-SAT

3-SAT is a special case of satisfiability (SAT) where
there are no more than 3 terms in each disjunction.
E.g., (P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬S) ∧ (¬P ∨ Q ∨ T) ∧ …

3-SAT is SAT, and 2-SAT is 3-SAT. The prior problems
were technically all 3-SAT.

All SAT problems can be reduced to 3-SAT problems.
3-SAT (and hence SAT) is NP-complete.

Here is one that uses all 3 terms:

Exercise:
Do there exist values for P, Q, and R such that:
(P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R) ∧ (¬P ∨ Q ∨ R) ∧ (¬Q ∨ ¬R) is true?
If so, prove it.
-/

-- Answer:

example: ∃(P Q R: Prop),
  (P ∨ Q ∨ R) ∧ (¬P ∨ ¬Q ∨ ¬R) ∧
   (¬P ∨ Q ∨ R) ∧ (¬Q ∨ ¬R) :=
begin
  apply exists.intro false,
  apply exists.intro true,
  apply exists.intro false,
  apply and.intro,
    -- prove false ∨ true ∨ false
    apply or.inr,
    exact or.inl true.intro,

    apply and.intro,
    -- prove ¬false ∨ ¬true ∨ ¬false
    exact or.inl false.elim,

    apply and.intro,
    -- prove ¬false ∨ true ∨ false
    exact or.inl false.elim,

    -- prove ¬true or ¬false
    exact or.inr false.elim
end


import data.real.basic
import data.real.nnreal
import tactic.ring

open real

-- https://sms.math.nus.edu.sg/Simo/IMO_Problems/00.pdf

lemma AM_GM_inequality (x y : ℝ) (hnnx : 0 ≤ x) (hnny : 0 ≤ y)
: sqrt (x * y) ≤ (x + y) / 2 :=
begin
    have h : 0 ≤ (sqrt x - sqrt y) * (sqrt x - sqrt y) := mul_self_nonneg _,
    rw [mul_sub] at h,
    iterate 2 { rw [sub_mul, ←sqrt_mul hnnx, ←sqrt_mul hnny] at h, },
    rw [sqrt_mul_self hnnx, sqrt_mul_self hnny, mul_comm] at h,
    linarith,
end

#check lt_irrefl

-- Note this didnt work with nnreals..
lemma IMO2000Q2_aux {x y z : ℝ} (hposx : 0 < x) (hposy : 0 < y) (hposz : 0 < z)
: (x - y + z) * (y - z + x) * (z - x + y) ≤ x * y * z :=
begin
    set u := x - y + z with hu,
    set v := y - z + x with hv,
    set w := z - x + y with hw,
    have hx : x = (u + v) / 2 := by rw [hu, hv]; ring,
    have hy : y = (v + w) / 2 := by rw [hv, hw]; ring,
    have hz : z = (w + u) / 2 := by rw [hw, hu]; ring,
    have hnnx := le_of_lt hposx,
    have hnny := le_of_lt hposy,
    have hnnz := le_of_lt hposz,
    have hnnxyz : 0 ≤ x * y * z := mul_nonneg (mul_nonneg hnnx hnny) hnnz,
    by_cases hnnu : 0 ≤ u; by_cases hnnv : 0 ≤ v; by_cases hnnw : 0 ≤ w,
    { -- u, v, w ≥ 0
      rw [hx, hy, hz],
      have huv := AM_GM_inequality u v hnnu hnnv,
      have hvw := AM_GM_inequality v w hnnv hnnw,
      have hwu := AM_GM_inequality w u hnnw hnnu,
      have hnnamuv : 0 ≤ (u + v) / 2 := div_nonneg (add_nonneg hnnu hnnv) (by linarith),
      have hnnamvw : 0 ≤ (v + w) / 2 := div_nonneg (add_nonneg hnnv hnnw) (by linarith),
      have hnnamwu : 0 ≤ (w + u) / 2 := div_nonneg (add_nonneg hnnw hnnu) (by linarith),
      have hnnuu := mul_nonneg hnnu hnnu,
      have hnnvv := mul_nonneg hnnv hnnv,
      have hnnww := mul_nonneg hnnw hnnw,
      have hnnsquv : 0 ≤ sqrt (u * v) := sqrt_nonneg _,
      have hnnsqvw : 0 ≤ sqrt (v * w) := sqrt_nonneg _,
      have hnnsqwu : 0 ≤ sqrt (w * u) := sqrt_nonneg _,
      have hnnuv := mul_nonneg hnnu hnnv,
      have hnnvw := mul_nonneg hnnv hnnw,
      have hnnwu := mul_nonneg hnnw hnnu,
      have h := mul_le_mul (mul_le_mul huv hvw hnnsqvw hnnamuv) hwu hnnsqwu (mul_nonneg hnnamuv hnnamvw),
      calc u * v * w = sqrt (u ^ 2) * sqrt (v ^ 2) * sqrt (w ^ 2)    : by rw [sqrt_sqr hnnu, sqrt_sqr hnnv, sqrt_sqr hnnw]
                 ... = sqrt (u * u) * (sqrt (v * v) * sqrt (w * w))  : by rw [pow_two u, pow_two v, pow_two w, mul_assoc]
                 ... = sqrt (u * u * (v * v * (w * w)))              : by rw [←sqrt_mul hnnvv, ←sqrt_mul hnnuu]
                 ... = sqrt ((u * v) * ((v * w) * (w * u)))          : by ring
                 ... = sqrt (u * v) * sqrt (v * w) * sqrt (w * u)    : by rw [sqrt_mul hnnuv, sqrt_mul hnnvw, mul_assoc]
                 ... ≤ ((u + v) / 2) * ((v + w) / 2) * ((w + u) / 2) : by exact h, },
    { -- v < 0.
      have h := mul_nonpos_iff.mpr (or.inl ⟨hnnv, le_of_not_le hnnw⟩),
      replace h := mul_nonpos_iff.mpr (or.inl ⟨hnnu, h⟩),
      rw [←mul_assoc] at h,
      exact le_trans h hnnxyz, },
    { -- w < 0.
      have h := mul_nonpos_iff.mpr (or.inl ⟨hnnu, le_of_not_le hnnv⟩),
      replace h := mul_nonpos_iff.mpr (or.inl ⟨hnnw, h⟩),
      rw [mul_comm w _] at h,
      exact le_trans h hnnxyz, },
    { -- v, w < 0, contradiction.
      exfalso,
      suffices hsuff : x < 0,
      { exact lt_irrefl x (lt_trans hsuff hposx), },
      replace hnnv := lt_of_not_ge hnnv,
      replace hnnw := lt_of_not_ge hnnw,
      have h := add_lt_add hnnv hnnw,
      rw [hv, hw] at h,
      linarith [h], },
    { -- u < 0.
      have h := mul_nonpos_iff.mpr (or.inl ⟨hnnv, le_of_not_le hnnu⟩),
      replace h := mul_nonpos_iff.mpr (or.inl ⟨hnnw, h⟩),
      rw [mul_comm w _, mul_comm v u] at h,
      exact le_trans h hnnxyz, },
    { -- u, w < 0, contradiction.
      exfalso,
      suffices hsuff : z < 0,
      { exact lt_irrefl z (lt_trans hsuff hposz), },
      replace hnnu := lt_of_not_ge hnnu,
      replace hnnw := lt_of_not_ge hnnw,
      have h := add_lt_add hnnu hnnw,
      rw [hu, hw] at h,
      linarith [h], },
    { -- u, v < 0, contradiction.
      exfalso,
      suffices hsuff : y < 0,
      { exact lt_irrefl y (lt_trans hsuff hposy), },
      replace hnnu := lt_of_not_ge hnnu,
      replace hnnv := lt_of_not_ge hnnv,
      have h := add_lt_add hnnu hnnv,
      rw [hu, hv] at h,
      linarith [h], },
    { -- u, v, w < 0, contradiction.
      exfalso,
      suffices hsuff : y < 0,
      { exact lt_irrefl y (lt_trans hsuff hposy), },
      replace hnnu := lt_of_not_ge hnnu,
      replace hnnv := lt_of_not_ge hnnv,
      have h := add_lt_add hnnu hnnv,
      rw [hu, hv] at h,
      linarith [h], },
end

lemma IMO2000Q2 (a b c : ℝ) (hposa : 0 < a) (hposb : 0 < b) (hposc : 0 < c) (h : a * b * c = 1) :
(a - 1 + 1 / b) * (b - 1 + 1 / c) * (c - 1 + 1 / a) ≤ 1 :=
begin
    have han0 : a ≠ 0 := ne_of_gt hposa,
    have hbn0 : b ≠ 0 := ne_of_gt hposb,
    have hcn0 : c ≠ 0 := ne_of_gt hposc,
    set! x := a * b with hx,
    set! y := b with hy,
    set! z := a * b * c with hz,
    have hposx : 0 < x := mul_pos hposa hposb,
    have hposy : 0 < y := hposb,
    have hposz : 0 < z := mul_pos (mul_pos hposa hposb) hposc,
    have hxn0 : x ≠ 0 := ne_of_gt hposx,
    have hyn0 : y ≠ 0 := ne_of_gt hposy,
    have hzn0 : z ≠ 0 := ne_of_gt hposz,
    have hxyzgt0 : x * y * z > 0,
    { rw [hx, hy, hz],
      exact mul_pos (mul_pos (mul_pos hposa hposb) hposb) (mul_pos (mul_pos hposa hposb) hposc), },
    have ha : a = x / y,
    { rw [hx, hy, mul_comm, mul_div_cancel_left _ hbn0], },
    have hb : b = y / z,
    { rw [hy, hz, h, div_one], },
    have hc : c = z / x,
    { rw [hz, hx, mul_div_cancel_left _ (mul_ne_zero han0 hbn0)], },
    rw [ha, hb, hc],
    iterate 3 { rw one_div_div, },
    apply (le_of_mul_le_mul_left _ hxyzgt0),
    rw mul_one,
    calc  x * y * z * ((x / y - 1 + z / y) * (y / z - 1 + x / z) * (z / x - 1 + y / x))
        = (y * (x / y - 1 + z / y)) * (z * (y / z - 1 + x / z)) * (x * (z / x - 1 + y / x))
        : by ring
    ... = (y * x / y - y + y * z / y) * (z * y / z - z + z * x / z) * (x * z / x - x + x * y / x)
        : by rw [mul_add y, mul_sub y, mul_one y, ←mul_div_assoc' y x y, ←mul_div_assoc' y z y,
                 mul_add z, mul_sub z, mul_one z, ←mul_div_assoc' z y z, ←mul_div_assoc' z x z,
                 mul_add x, mul_sub x, mul_one x, ←mul_div_assoc' x z x, ←mul_div_assoc' x y x]
    ... = (x - y + z) * (y - z + x) * (z - x + y)
        : by iterate 2 { rw [mul_div_cancel_left _ hxn0, mul_div_cancel_left _ hyn0, mul_div_cancel_left _ hzn0] }
    ... ≤ x * y * z
        : by exact (IMO2000Q2_aux hposx hposy hposz),
end


-- Keith
-- Chapter 2

import data.set algebra.binary algebra.group classical

open nat

-- I. Proofs about parity

--[KS] An integer x is called even if there is some integer k such that x = 2k.

definition even (x : nat) := ∃ k, x = 2 * k.

--[KS] An integer x is called odd if there is some integer k such that x = 2k + 1.

definition odd (x : nat) := ∃ k, x = 2 * k + 1.

notation x `²` := (x * x)

/- [KS]
Theorem: If x is even, then x^2 is even.
Proof: Let x be any even integer. Since x is even, there is some integer k such that x = 2k. This
means that x^2 = (2k)^2 = 4k = 2(2k)^2. Since (2k)^2 is an integer, this means that there is some integer m
(namely, (2k)^2) such that x^2 = 2m. Thus x^2 is even."
-/

theorem x_even_x2_even : ∀ (x : nat), even x → even (x²) :=
  -- Let x be an even integer
  take (x : nat),
  assume (even_x : even x),
  -- Since x is even, there is some integer k such that x = 2k.
  obtain (k : nat) (x_eq_2k : x = 2 * k), from even_x,
  -- This means that x^2 = (2k)^2 = 4k = 2(2k)^2
  have x2_eq_2m : x² = 2 * (2 * k²), from
    calc x² = (2 * k)² : x_eq_2k
         ... = 4 * k² : sorry
         ... = 2 * (2 * k²) : sorry,
  -- this means that there is some integer m (namely, (2k)^2) such that x^2 = 2m.
  have x2_div2 : ∃ m, x² = 2 * m, from exists.intro (2 * k²) x2_eq_2m,
  -- Thus x^2 is even.
  show even (x²), from x2_div2

/- [KS]
Theorem: If m is even and n is odd, then mn is even.
Proof: Let m be any even number and n be any odd number. Then m = 2r for some integer r, and
n = 2s + 1 for some integer s. This means that mn = (2r)(2s + 1) = 2(r(2s + 1)). This means that
mn = 2k for some integer k (namely, r(2s + 1)), so mn is even.
-/

theorem even_mul_odd_even : ∀ (m n : nat), even m → odd n → even (m * n) :=
  -- Let m be any even number and n be any odd number.
  take (m n : nat),
  assume (even_m : even m) (odd_n : odd n),
  -- Then m = 2r for some integer r,
  obtain (r : nat) (m_eq_2r : m = 2 * r), from even_m,
  -- and n = 2s + 1 for some integer s.
  obtain (s : nat) (n_eq_2s_p1 : n = 2 * s + 1), from odd_n,
  -- This means that mn = (2r)(2s + 1) = 2(r(2s + 1)).
  have mn_eq_2t : m * n = 2 * (r * (2 * s + 1)), from
  calc m * n = (2 * r) * (2 * s + 1) : sorry
       ... = 2 * (r * (2 * s + 1)) : sorry,
  -- This means that mn = 2k for some integer k (namely, r(2s + 1)), so mn is even.
  show even (m * n), from exists.intro (r * (2 * s + 1)) mn_eq_2t

-- [KS] Two numbers have the same parity if they are both odd or both even

definition both {T : Type} (h : T → Prop) (x y : T) := h x ∧ h y
definition same_parity (x y : nat) := both even x y ∨ both odd x y
definition same_parity_odd1 {x y : nat} : same_parity x y → odd x → odd y := sorry
definition same_parity_odd2 {x y : nat} : same_parity x y → odd y → odd x := sorry

/- [KS]
Theorem: If m and n have the same parity, then m + n is even.
Proof: Let m and n be any two integers with the same parity. Then there are two cases to consider:
Case 1: m and n are even. Then m = 2r for some integer r and n = 2s for some integer s. Therefore,
m + n = 2r + 2s = 2(r + s). Thus m + n = 2k for some integer k (namely, r + s), so m + n is even.
Case 2: m and n are odd. Then m = 2r + 1 for some integer r and n = 2s + 1 for some integer s.
Therefore, m + n = 2r + 1 + 2s + 1 = 2r + 2s + 2 = 2(r + s + 1). Thus m + n = 2k for some integer k
(namely, r + s + 1), so m + n is even.
-/

theorem same_parity_add_even : ∀ {x y : nat}, same_parity x y → even (x + y) :=
  -- Let m and n be any two integers with the same parity.
  take (x y : nat),
  assume (sp_xy : same_parity x y),
  -- Then there are two cases to consider:
  have two_cases : (even x ∧ even y) ∨ (odd x ∧ odd y), from sp_xy,
  or.elim two_cases

(-- Case 1: m and n are even.
           take (both_even : even x ∧ even y),
           have even_x : even x, from and.elim_left both_even,
           have even_y : even y, from and.elim_right both_even,
          -- Then m = 2r for some integer r and n = 2s for some integer s.
           obtain (r : nat) (x_eq_2r : x = 2 * r), from even_x,
           obtain (s : nat) (y_eq_2s : y = 2 * s), from even_y,
          -- Therefore, m + n = 2r + 2s = 2(r + s).
           have xpy_eq_2t : x + y = 2 * (r + s), from
             calc x + y = 2 * r + 2 * s : sorry
                  ... = 2 * (r + s) : sorry,
         -- Thus m + n = 2k for some integer k (namely, r + s), so m + n is even.
           show even (x + y), from exists.intro (r + s) xpy_eq_2t)

          (-- Case 2: m and n are odd.
          take (both_odd : odd x ∧ odd y),
           have odd_x : odd x, from and.elim_left both_odd,
           have odd_y : odd y, from and.elim_right both_odd,
          -- Then m = 2r + 1 for some integer r and n = 2s + 1 for some integer s.
           obtain (r : nat) (x_eq_2r_p1 : x = 2 * r + 1), from odd_x,
           obtain (s : nat) (y_eq_2s_p1 : y = 2 * s + 1), from odd_y,
          -- Therefore, m + n = 2r + 1 + 2s + 1 = 2r + 2s + 2 = 2(r + s + 1).
           have xpy_eq_2t : x + y = 2 * (r + s + 1), from
             calc x + y = 2 * r + 1 + 2 * s + 1 : sorry
                  ... = 2 * r + 2 * s + 2 : sorry
                  ... = 2 * (r + s + 1) : sorry,
          --  Thus m + n = 2k for some integer k (namely, r + s + 1), so m + n is even.
           show even (x + y), from exists.intro (r + s + 1) xpy_eq_2t)

theorem even_or_odd : ∀ (n : nat), even n ∨ odd n := sorry
theorem not_even_and_odd : ∀ {n : nat}, even n → odd n → false := sorry

/- [KS]
Theorem: If n is even and m is an integer, then n + m has the same parity as m.
Proof: Consider any even integer n. Now, consider any integer m and the sum n + m. We consider
two possibilities for m:
Case 1: m is even. Then m and n have the same parity, so by our previous result (if m and n have the
same parity, then m + n is even) we know that m + n is even. Therefore m and m + n have the same
parity.
Case 2: m is odd. Since n is even, n = 2r for some integer r, and since m is odd, m = 2s + 1 for some
integer s. Then n + m = 2r + 2s + 1 = 2(r + s) + 1. This means that n + m = 2k + 1 for some k
(namely, r + s), so n + m is odd. Therefore m and m + n have the same parity.
-/

theorem even_add_sp : ∀ {n : nat}, even n → ∀ (m : nat), same_parity (n + m) m :=
  -- Consider any even integer n.
  take (n : nat),
  assume (even_n : even n),
  -- Now, consider any integer m [and the sum n + m]. We consider two possibilities for m:
  take (m : nat),
  or.elim (even_or_odd m)
          (-- Case 1: m is even.
           assume (even_m : even m),
           -- Then m and n have the same parity,
           have sp_nm : same_parity n m, from or.inl (and.intro even_n even_m),
           -- so by our previous result (if m and n have the same parity, then m + n is even) we know that m + n is even.
           have even_mpn : even (n + m), from same_parity_add_even sp_nm,
           -- Therefore m and m + n have the same parity.
           show same_parity (n + m) m, from or.inl (and.intro even_mpn even_m))
          (-- Case 2: m is odd.
           assume (odd_m : odd m),
           -- Since n is even, n = 2r for some integer r,
           obtain (r : nat) (n_eq_2r : n = 2 * r), from even_n,
           --  and since m is odd, m = 2s + 1 for some integer s.
           obtain (s : nat) (m_eq_2s_p1 : m = 2 * s + 1), from odd_m,
           -- Then n + m = 2r + 2s + 1 = 2(r + s) + 1.
           have npm_eq_2t_p1 : n + m = 2 * (r + s) + 1, from
             calc n + m = 2 * r + 2 * s + 1 : sorry
                  ... = 2 * (r + s) + 1 : sorry,
           -- This means that n + m = 2k + 1 for some k (namely, r + s), so n + m is odd.
           have odd_npm : odd (n + m), from exists.intro (r + s) npm_eq_2t_p1,
           -- Therefore m and m + n have the same parity.
           show same_parity (n + m) m, from or.inr (and.intro odd_npm odd_m))

-- Keith, chapter 2
-- proofs about parity
open set binary

variable {T : Type}

/- [KS]
Theorem: For any sets A and B, A ∩ B ⊆ A.
Proof: Consider any sets A and B. We want to show that A ∩ B ⊆ A. By the definition of subset, this
means that we need to show that for any x ∈ A ∩ B, x ∈ A. So consider any x ∈ A ∩ B. By the definition
of intersection, x ∈ A ∩ B means that x ∈ A and x ∈ B. Therefore, if x ∈ A ∩ B, x ∈ A. Since our
choice of x was arbitrary, A ∩ B ⊆ A.
-/

theorem inter_sub : ∀ (A B : set T), A ∩ B ⊆ A :=
  -- Consider any sets A and B.
  take (A B : set T),
  -- We want to show that A ∩ B ⊆ A.
  show A ∩ B ⊆ A, from
  -- By the definition of subset, this means that we need to show that for any x ∈ A ∩ B, x ∈ A.
  show ∀ x, x ∈ A ∩ B → x ∈ A, from
  -- So consider any x ∈ A ∩ B.
  take (x : T),
  assume (x_in_inter : x ∈ A ∩ B),
  -- By the definition of intersection, x ∈ A ∩ B means that x ∈ A and x ∈ B.
  have x_in_A : x ∈ A, from and.elim_left x_in_inter,
  have x_in_B : x ∈ B, from and.elim_right x_in_inter,
  -- Therefore, [if x ∈ A ∩ B], x ∈ A.
  show x ∈ A, from x_in_A
  -- Since our choice of x was arbitrary, A ∩ B ⊆ A.

/- [KS]
Theorem: For any sets A and B, A ⊆ A ∪ B.
Proof: Consider any sets A and B. We want to show that A ⊆ A ∪ B. To do this, we show that for
any x ∈ A, that x ∈ A ∪ B as well. Note that by definition, x ∈ A ∪ B iff x ∈ A or x ∈ B.
Consider any x ∈ A. It is therefore true that x ∈ A or x ∈ B, since we know x ∈ A. Consequently,
x ∈ A ∪ B. Since our choice of x was arbitrary, this shows that any x ∈ A also satisfies x ∈ A ∪ B.
Consequently, A ⊆ A ∪ B, as required.
-/

theorem union_sup : ∀ (A B : set T), A ⊆ A ∪ B :=
  -- Consider any sets A and B.
  take (A B : set T),
  -- We want to show that A ⊆ A ∪ B.
  show A ⊆ A ∪ B, from
  -- To do this, we show that for any x ∈ A, that x ∈ A ∪ B as well.
  show ∀ x, x ∈ A → x ∈ A ∪ B, from
  -- [Note that by definition, x ∈ A ∪ B iff x ∈ A or x ∈ B.]
  -- Consider any x ∈ A.
  take (x : T),
  assume (x_in_A : x ∈ A),
  -- It is therefore true that x ∈ A or x ∈ B, since we know x ∈ A.
  have x_in_A_or_B : x ∈ A ∨ x ∈ B, from or.inl x_in_A,
  -- Consequently, x ∈ A ∪ B.
  show x ∈ A ∪ B, from x_in_A_or_B
  -- Since our choice of x was arbitrary, this shows that any x ∈ A also satisfies x ∈ A ∪ B. Consequently, A ⊆ A ∪ B, as required.

definition set_minus [reducible] (A B : set T) :=
  λ x, x ∈ A ∧ ¬ x ∈ B

notation a - b := set_minus a b

-- probably in the 'classical' library

theorem prop_demorgan : ∀ {A B : Prop}, ¬ (A ∧ B) → ¬ A ∨ ¬ B :=
  take (A B : Prop),
  take notAandB : ¬ (A ∧ B),
  or.elim (em A)
          (take a : A,
           or.elim (em B)
                   (take b : B, absurd (and.intro a b) notAandB)
                   (take nb : ¬ B, or.inr nb))
          (take na : ¬ A, or.inl na)

/- [KS]
Theorem: For any sets A, B, and C, we have C – (A ∩ B) ⊆ (C – A) ∪ (C – B)
Proof: Consider any sets A, B, and C. We will show C – (A ∩ B) ⊆ (C – A) ∪ (C – B). By definition,
this is true if for any x ∈ C – (A ∩ B), we also have x ∈ (C – A) ∪ (C – B). So consider any
x ∈ C – (A ∩ B). By the definition of set difference, this means that x ∈ C and x ∉ A ∩ B. Since
x ∉ A ∩ B, we know that it is not the case that both x ∈ A and x ∈ B. Consequently, it must be true
that either x ∉ A or x ∉ B. We consider these two cases individually:
Case 1: x ∉ A. Since we know that x ∈ C and x ∉ A, we know that x ∈ C – A. By our earlier result, we
therefore have that x ∈ (C – A) ∪ (C – B).
Case 2: x ∉ B. Since we know that x ∈ C and x ∉ B, we know that x ∈ C – B. By our earlier result, we
therefore have that x ∈ (C – A) ∪ (C – B).
In either case we have that x ∈ (C – A) ∪ (C – B). Since our choice of x was arbitrary, we have that
C – (A ∩ B) ⊆ (C – A) ∪ (C – B) as required.
-/

theorem sm_inter_ss_union_sm : ∀ (A B C : set T), C - (A ∩ B) ⊆ (C - A) ∪ (C - B) :=
  -- Consider any sets A, B, and C.
  take (A B C : set T),
  -- We will show C – (A ∩ B) ⊆ (C – A) ∪ (C – B).
  show C - (A ∩ B) ⊆ (C - A) ∪ (C - B), from
  -- By definition, this is true if for any x ∈ C – (A ∩ B), we also have x ∈ (C – A) ∪ (C – B).
  show ∀ x, x ∈ C - (A ∩ B) → x ∈ (C - A) ∪ (C - B), from
  -- So consider any x ∈ C – (A ∩ B).
  take (x : T),
  assume (x_in_lhs : x ∈ C - (A ∩ B)),
  -- By the definition of set difference, this means that x ∈ C and x ∉ A ∩ B.
  have x_in_C : x ∈ C, from and.elim_left x_in_lhs,
  have x_nin_AcapB : ¬ x ∈ A ∩ B, from and.elim_right x_in_lhs,
  -- Since x ∉ A ∩ B, we know that it is not the case that both x ∈ A and x ∈ B.
  have x_nin_A_and_B : ¬ (x ∈ A ∩ B), from and.elim_right x_in_lhs,
  -- Consequently, it must be true that either x ∉ A or x ∉ B.
  have two_cases : ¬ x ∈ A ∨ ¬ x ∈ B, from prop_demorgan x_nin_A_and_B,
  -- We consider these two cases individually:
  or.elim two_cases
          (-- Case 1: x ∉ A.
           assume (x_nin_A : ¬ x ∈ A),
           -- Since we know that x ∈ C and x ∉ A, we know that x ∈ C – A.
           have x_in_CmA : x ∈ C - A, from and.intro x_in_C x_nin_A,
           -- [By our earlier result], we therefore have that x ∈ (C – A) ∪ (C – B).
           show x ∈ (C - A) ∪ (C - B), from or.inl x_in_CmA)
          (-- Case 2: x ∉ B.
           assume (x_nin_B : ¬ x ∈ B),
           -- Since we know that x ∈ C and x ∉ B, we know that x ∈ C – B.
           have x_in_CmB : x ∈ C - B, from and.intro x_in_C x_nin_B,
           -- [By our earlier result], we therefore have that x ∈ (C – A) ∪ (C – B). [DHS: not without commutativity you don't]
           show x ∈ (C - A) ∪ (C - B), from or.inr (and.intro x_in_C x_nin_B))
  --In either case we have that x ∈ (C – A) ∪ (C – B). Since our choice of x was arbitrary, we have that C – (A ∩ B) ⊆ (C – A) ∪ (C – B) as required.

/- [KS]
Lemma: For any sets A and B, A = B if and only if A ⊆ B and B ⊆ A.
Proof: We prove both directions of implication.
( ⇒ ) First, we show that, for any sets A and B, if A = B, then A ⊆ B and B ⊆ A. If A = B, consider
any x ∈ A. Since A = B, this means that x ∈ B. Since our choice of x was arbitrary, any x ∈ A satisfies
x ∈ B, so A ⊆ B. Similarly, consider any x ∈ B, then since A = B, x ∈ A as well. Since our choice of x
was arbitrary, any x ∈ B satisfies x ∈ A, so B ⊆ A.
( ⇐) Next, we prove that if A ⊆ B and B ⊆ A, then A = B. Consider any two sets A and B where
A ⊆ B and B ⊆ A. We need to prove that A = B. Since A ⊆ B, for any x ∈ A, x ∈ B as well. Since
B ⊆ A, for any x ∈ B, x ∈ A as well. Thus every element of A is in B and vice-versa, so the two sets
have the same elements.
-/

lemma eqv_ss : ∀ {A B : set T}, A ∼ B ↔ A ⊆ B ∧ B ⊆ A :=
  -- We prove both directions of implication.
  take (A B : set T),
  iff.intro
    (-- ( ⇒ ) First, we show that, for any sets A and B, if A = B, then A ⊆ B and B ⊆ A.
     show A ∼ B → A ⊆ B ∧ B ⊆ A, from
    -- If A = B,
     take A_eqv_B : A ∼ B,
    -- [DHS] states the goal later
     have A_ss_B : A ⊆ B, from
    -- consider any x ∈ A.
     assume (x : T) (x_in_A : x ∈ A),
    -- Since A = B, this means that x ∈ B.
     have x_in_B : x ∈ B, from iff.elim_left (A_eqv_B x) x_in_A,
    -- [Since our choice of x was arbitrary, any x ∈ A satisfies x ∈ B, so A ⊆ B.]
     show x ∈ B, from x_in_B,

    -- [DHS] states the goal later,
    have B_ss_A : B ⊆ A, from
    -- Similarly, consider any x ∈ B,
     assume (x : T) (x_in_B : x ∈ B),
    -- then since A = B, x ∈ A as well.
     have x_in_A : x ∈ A, from iff.elim_right (A_eqv_B x) x_in_B,
    -- [Since our choice of x was arbitrary, any x ∈ B satisfies x ∈ A, so B ⊆ A.]
     show x ∈ A, from x_in_A,

    and.intro A_ss_B B_ss_A)

  (-- ( ⇐) Next, we prove that if A ⊆ B and B ⊆ A, then A = B.
    show A ⊆ B ∧ B ⊆ A → A ∼ B, from
    -- Consider any two sets A and B where A ⊆ B and B ⊆ A.
    take H : A ⊆ B ∧ B ⊆ A,
    have A_ss_B : A ⊆ B, from and.elim_left H,
    have B_ss_A : B ⊆ A, from and.elim_right H,
    -- We need to prove that A = B.
    show A ∼ B, from
    take x,
    -- [DHS] states strategy afterwards
    iff.intro (-- Since A ⊆ B, for any x ∈ A, x ∈ B as well.
                assume x_in_A : x ∈ A,
               show x ∈ B, from A_ss_B x x_in_A)
              (-- Since B ⊆ A, for any x ∈ B, x ∈ A as well.
               assume x_in_B : x ∈ B,
               show x ∈ A, from B_ss_A x x_in_B)
    -- Thus every element of A is in B and vice-versa, so the two sets have the same elements.
    )

definition sym_diff [reducible] (A B : set T) : set T := λ x, (x ∈ A ∧ ¬ x ∈ B) ∨ (x ∈ B ∧ ¬ x ∈ A)

infix `Δ`:101 := sym_diff

-- (taken implicitly as a lemma in Keith's notes)
lemma sym_diff_nocases : ∀ {A B : set T} (x : T), (x ∈ A ∨ x ∈ B) → (¬ x ∈ A ∨ ¬ x ∈ B) → x ∈ A Δ B :=
  take (A B : set T),
  take x : T,
  assume x_in_A_or_B : x ∈ A ∨ x ∈ B,
  assume x_nin_A_or_B : ¬ x ∈ A ∨ ¬ x ∈ B,
  or.elim x_in_A_or_B
          (take x_in_A : x ∈ A,
           or.elim x_nin_A_or_B
                   (take x_nin_A : ¬ x ∈ A, absurd x_in_A x_nin_A)
                   (take x_nin_B : ¬ x ∈ B, or.inl (and.intro x_in_A x_nin_B)))
          (take x_in_B : x ∈ B,
           or.elim x_nin_A_or_B
                   (take x_nin_A : ¬ x ∈ A, or.inr (and.intro x_in_B x_nin_A))
                   (take x_nin_B : ¬ x ∈ B, absurd x_in_B x_nin_B))

/- [KS]
Lemma 1: (A ∪ B) – (A ∩ B) ⊆ A Δ B.
Proof of Lemma 1: We will show that for any x ∈ (A ∪ B) – (A ∩ B), x ∈ A Δ B. So consider any
x ∈ (A ∪ B) – (A ∩ B). This means that x ∈ A ∪ B, but x ∉ A ∩ B. Since x ∈ A ∪ B, we know that
x ∈ A or x ∈ B. Since x ∉ A ∩ B, we know that x is not contained in both A and B. We thus have that
x is in at least one of A and B, but not both. Consequently, x ∈ A Δ B. Since our choice of x was arbitrary,
we therefore have that (A ∪ B) – (A ∩ B) ⊆ A Δ B.
-/

-- [DHS]#DESIGN Keith uses an unstated and unproved lemma, arguably

lemma sym_diff_alt_1 : ∀ {A B : set T}, (A ∪ B) - (A ∩ B) ⊆ A Δ B :=
  take (A B : set T),
  -- We will show that for any x ∈ (A ∪ B) – (A ∩ B), x ∈ A Δ B.
  show ∀ x, x ∈ (A ∪ B) - (A ∩ B) → x ∈ A Δ B, from
  -- So consider any x ∈ (A ∪ B) – (A ∩ B).
  take (x : T),
  assume (x_in_lhs : x ∈ (A ∪ B) - (A ∩ B)),
  -- This means that x ∈ A ∪ B,
  have x_in_A_cup_B : x ∈ A ∪ B, from and.elim_left x_in_lhs,
  -- but x ∉ A ∩ B.
  have x_nin_A_cap_B : ¬ x ∈ A ∩ B, from and.elim_right x_in_lhs,
  -- Since x ∈ A ∪ B, we know that x ∈ A or x ∈ B.
  have x_in_A_or_B : x ∈ A ∨ x ∈ B, from x_in_A_cup_B,
  -- Since x ∉ A ∩ B, we know that x is not contained in both A and B. We thus have that x is in at least one of A and B, but not both.
  have x_nin_A_or_x_nin_B : ¬ x ∈ A ∨ ¬ x ∈ B, from prop_demorgan x_nin_A_cap_B,
  -- Consequently, x ∈ A Δ B.
  show x ∈ A Δ B, from sym_diff_nocases x x_in_A_or_B x_nin_A_or_x_nin_B
  -- Since our choice of x was arbitrary, we therefore have that (A ∪ B) – (A ∩ B) ⊆ A Δ B.

/- [KS]
Lemma 2: A Δ B ⊆ (A ∪ B) – (A ∩ B)
Proof of Lemma 2: We will show that for any x ∈ A Δ B, x ∈ (A ∪ B) – (A ∩ B). Consider any
x ∈ A Δ B. Then either x ∈ A and x ∉ B, or x ∈ B and x ∉ A. We consider these cases separately:
Case 1: x ∈ A and x ∉ B. Since x ∈ A, x ∈ A ∪ B. Since x ∉ B, x ∉ A ∩ B. Consequently,
x ∈ (A ∪ B) – (A ∩ B).
Case 2: x ∈ B and x ∉ A. Since x ∈ B, x ∈ A ∪ B. Since x ∉ A, x ∉ A ∩ B. Consequently,
x ∈ (A ∪ B) – (A ∩ B).
In either case, x ∈ (A ∪ B) – (A ∩ B). Since our choice of x was arbitrary, we have that
A Δ B ⊆ (A ∪ B) – (A ∩ B).
-/

lemma sym_diff_alt_2 : ∀ {A B : set T}, A Δ B ⊆ (A ∪ B) - (A ∩ B) :=
  take (A B : set T),
  -- We will show that for any x ∈ A Δ B, x ∈ (A ∪ B) – (A ∩ B).
  show ∀ x, x ∈ A Δ B → x ∈ (A ∪ B) - (A ∩ B), from
  -- Consider any x ∈ A Δ B.
  take (x : T),
  assume (x_in_sdAB : x ∈ A Δ B),
  -- Then either x ∈ A and x ∉ B, or x ∈ B and x ∉ A. We consider these cases separately:
  or.elim x_in_sdAB
          (-- Case 1: x ∈ A and x ∉ B.
           assume H : x ∈ A ∧ ¬ x ∈ B,
           have x_in_A : x ∈ A, from and.elim_left H,
           have x_nin_B : ¬ x ∈ B, from and.elim_right H,
           -- Since x ∈ A, x ∈ A ∪ B.
           have x_in_AcupB : x ∈ A ∪ B, from or.inl x_in_A,
           -- Since x ∉ B, x ∉ A ∩ B.
           have x_nin_AcapB : ¬ x ∈ A ∩ B, from take x_in_AcapB, absurd (and.elim_right x_in_AcapB) x_nin_B,
           -- Consequently, x ∈ (A ∪ B) – (A ∩ B).
           show x ∈ (A ∪ B) - (A ∩ B), from and.intro x_in_AcupB x_nin_AcapB)
          (-- Case 2: x ∈ B and x ∉ A.
           assume H : x ∈ B ∧ ¬ x ∈ A,
           have x_in_B : x ∈ B, from and.elim_left H,
           have x_nin_A : ¬ x ∈ A, from and.elim_right H,
           -- Since x ∈ B, x ∈ A ∪ B.
           have x_in_AcupB : x ∈ A ∪ B, from or.inr x_in_B,
           -- Since x ∉ A, x ∉ A ∩ B.
           have x_nin_AcapB : ¬ x ∈ A ∩ B, from take x_in_AcapB, absurd (and.elim_left x_in_AcapB) x_nin_A,
           -- Consequently, x ∈ (A ∪ B) – (A ∩ B).
           show x ∈ (A ∪ B) - (A ∩ B), from and.intro x_in_AcupB x_nin_AcapB)
-- In either case, x ∈ (A ∪ B) – (A ∩ B). Since our choice of x was arbitrary, we have that A Δ B ⊆ (A ∪ B) – (A ∩ B).

/-
Proof of Lemma 2: We will show that for any x ∈ A Δ B, x ∈ (A ∪ B) – (A ∩ B). Consider any
x ∈ A Δ B. Then either x ∈ A and x ∉ B, or x ∈ B and x ∉ A. Assume without loss of generality that
x ∈ A and x ∉ B. Since x ∈ A, x ∈ A ∪ B. Since x ∉ B, x ∉ A ∩ B, so x ∈ (A ∪ B) – (A ∩ B). Since our
choice of x was arbitrary, we have that A Δ B ⊆ (A ∪ B) – (A ∩ B).
-/

-- [DHS]#DESIGN w.l.o.g. actually quite subtle, since it depends on 2(!) commutativity lemmas

theorem union_com : commutative (@union T) := sorry
theorem inter_com : commutative (@inter T) := sorry

-- Here is the abstract lemma, that Keith does not show explicitly

lemma sym_diff_alt_2a : ∀ {A B : set T} {x : T}, x ∈ A - B → x ∈ (A ∪ B) - (A ∩ B) :=
  take (A B : set T) (x : T),
  -- Assume [without loss of generality] that x ∈ A and x ∉ B.
  assume (x_in_AmB : x ∈ A - B),
  have x_in_A : x ∈ A, from and.elim_left x_in_AmB,
  have x_nin_B : ¬ x ∈ B, from and.elim_right x_in_AmB,
  -- Since x ∈ A, x ∈ A ∪ B.
  have x_in_AcupB : x ∈ A ∪ B, from or.inl x_in_A,
  -- Since x ∉ B, x ∉ A ∩ B,
  have x_nin_AcapB : ¬ x ∈ A ∩ B, from take x_in_AcapB, absurd (and.elim_right x_in_AcapB) x_nin_B,
  -- so x ∈ (A ∪ B) – (A ∩ B)
  show x ∈ (A ∪ B) - (A ∩ B), from and.intro x_in_AcupB x_nin_AcapB

open eq.ops

definition wlog (xy : A ∨ B) (b1 : A → C) : C := or.elim xy b1 b1

lemma sym_diff_alt_2b : ∀ {A B : set T}, A Δ B ⊆ (A ∪ B) - (A ∩ B) :=
  take (A B : set T),
  -- We will show that for any x ∈ A Δ B, x ∈ (A ∪ B) – (A ∩ B).
  show ∀ x, x ∈ A Δ B → x ∈ (A ∪ B) - (A ∩ B), from
  -- Consider any x ∈ A Δ B.
  take x,
  assume (x_in_sdAB : x ∈ A Δ B),
  -- Then either x ∈ A and x ∉ B, or x ∈ B and x ∉ A.
  wlog x_in_sdAB (assume x_in_AmB : x ∈ A - B, sym_diff_alt_2a x_in_AmB)

/-  or.elim x_in_sdAB
          (assume x_in_AmB : x ∈ A - B, sym_diff_alt_2a x_in_AmB)
          (assume x_in_BmA : x ∈ B - A,
           (inter_com B A) ▸ (union_com B A) ▸ sym_diff_alt_2a x_in_BmA)
-/
/- [KS]
Theorem: For any sets A and B, (A ∪ B) – (A ∩ B) = A Δ B.
Proof of Theorem: By Lemma 1, (A ∪ B) – (A ∩ B) ⊆ A Δ B. By Lemma 2,
A Δ B ⊆ (A ∪ B) – (A ∩ B). Since each set is a subset of the other, by our earlier lemma we have that
(A ∪ B) – (A ∩ B) = A Δ B
-/
theorem sym_diff_alt : ∀ (A B : set T), (A ∪ B) - (A ∩ B) ∼ A Δ B :=
  take (A B : set T),
  -- By Lemma 1, (A ∪ B) – (A ∩ B) ⊆ A Δ B.
  have l_to_r : (A ∪ B) - (A ∩ B) ⊆ A Δ B, from sym_diff_alt_1,
  -- By Lemma 2, A Δ B ⊆ (A ∪ B) – (A ∩ B).
  have r_to_l : A Δ B ⊆ (A ∪ B) - (A ∩ B), from sym_diff_alt_2,
  -- Since each set is a subset of the other, by our earlier lemma we have that (A ∪ B) – (A ∩ B) = A Δ B
  show (A ∪ B) - (A ∩ B) ∼ A Δ B, from iff.elim_right eqv_ss (and.intro sym_diff_alt_1 sym_diff_alt_2)

-- [DHS]
-- #DESGN
-- Issue #1: naming hypotheses
-- Issue #2: 'obvious' lemmas
-- Issue #3: w.l.o.g.

/- [KS]
Theorem: If m and n have opposite parity, m + n is odd.
Proof: Without loss of generality, assume that m is odd and n is even. Since m is odd, there exists an
integer r such that m = 2r + 1. Since n is even, there exists an integer s such that n = 2s. Then
m + n = 2r + 1 + 2s = 2(r + s) + 1. Consequently, m + n is odd.
-/

definition opposite_parity (x y : nat) := (odd x ∧ even y) ∨ (odd y ∧ even x)

lemma add_comm : ∀ (x y : nat), x + y = y + x := sorry

set_option formatter.hide_full_terms false

/- [DHS]#DESIGN how is [suffices_to_show]? I think it is pretty reasonable. -/

definition suffices_to_show {Goal : Prop} (P : Prop) (H : P → Goal) (p : P) : Goal := H p

lemma opposite_parity_sum_odd : ∀ (m n : nat), opposite_parity m n → odd (m + n) :=

  -- Without loss of generality, assume that m is odd and n is even.
  suffices_to_show (∀ (m n : nat), odd m ∧ even n → odd (m + n))

    (assume (lem : ∀ (m n : nat), odd m ∧ even n → odd (m + n))
            (m n : nat) (opp_mn : opposite_parity m n),
      or.elim opp_mn (assume H : odd m ∧ even n, lem m n H) (assume H : odd n ∧ even m, (add_comm n m) ▸ (lem n m H)))

    (take (m n : nat),
    assume H : odd m ∧ even n,
  -- Since m is odd, there exists an integer r such that m = 2r + 1.
    obtain r (m_eq_2r_p1 : m = 2 * r + 1), from and.elim_left H,
  -- Since n is even, there exists an integer s such that n = 2s.
    obtain s (n_eq_2s : n = 2 * s), from and.elim_right H,
  -- Then m + n = 2r + 1 + 2s = 2(r + s) + 1.
    have mpn_eq_2t_p1 : m + n = 2 * (r + s) + 1, from
      calc m + n = 2 * r + 1 + 2 * s : by rewrite [m_eq_2r_p1, n_eq_2s]
           ...= 2 * (r + s) + 1 : sorry,
  -- Consequently, m + n is odd.
    show odd (m + n), from exists.intro (r + s) mpn_eq_2t_p1)

-- Proofs with vacuous truths
/- [KS]
Theorem: For any sets A and B, if A ⊆ B, then A – B = Ø.

Lemma 1: For any sets A and B, if A ⊆ B, then Ø ⊆ A – B.
Lemma 2: For any sets A and B, if A ⊆ B, then A – B ⊆ Ø.

Proof of Lemma 1: We need to show that every element x ∈ Ø also satisfies x ∈ A – B. But this is
vacuously true, as there are no x satisfying x ∈ Ø.

Proof of Lemma 2: We need to show that any x ∈ A – B also satisfies x ∈ Ø. Consider any x ∈ A – B.
This means that x ∈ A and x ∉ B. Since A ⊆ B and since x ∈ A, we know that x ∈ B. But this means
simultaneously that x ∈ B and x ∉ B. Consequently, there are no x ∈ A – B, so the claim that any
x ∈ A – B also satisfies x ∈ Ø is vacuously true.

Proof of Theorem: Consider any sets A and B such that A ⊆ B. By Lemma 1, we have that
Ø ⊆ A – B. By Lemma 2, we have that A – B ⊆ Ø. Thus by our earlier lemma, A – B = Ø as required.
-/

lemma vacuous_set1a : ∀ {A B : set T}, A ⊆ B → ∅ ⊆ A - B :=
  take (A B : set T) (A_ss_B : A ⊆ B),
  -- We need to show that every element x ∈ Ø also satisfies x ∈ A – B.
  show ∀ x, x ∈ ∅ → x ∈ A - B, from
  -- But this is vacuously true, as there are no x satisfying x ∈ Ø.
  assume x x_in_empty,
  false.rec _ x_in_empty

lemma vacuous_set1b : ∀ {A B : set T}, A ⊆ B → A - B ⊆ ∅ :=
  take (A B : set T) (A_ss_B : A ⊆ B),
  -- We need to show that any x ∈ A – B also satisfies x ∈ Ø.
  show ∀ x, x ∈ A - B → x ∈ ∅, from
  -- Consider any x ∈ A – B.
  take x (x_in_AmB : x ∈ A - B),
  -- This means that x ∈ A and x ∉ B.
  have x_in_A : x ∈ A, from and.elim_left x_in_AmB,
  have x_nin_B : ¬ x ∈ B, from and.elim_right x_in_AmB,
  -- Since A ⊆ B and since x ∈ A, we know that x ∈ B.
  have x_in_B : x ∈ B, from A_ss_B x x_in_A,
  -- But this means simultaneously that x ∈ B and x ∉ B.
  -- Consequently, there are no x ∈ A – B, so the claim that any x ∈ A – B also satisfies x ∈ Ø is vacuously true.
  absurd x_in_B x_nin_B

theorem vacuous_set1 : ∀ {A B : set T}, A ⊆ B → A - B ∼ ∅ :=
  -- Consider any sets A and B such that A ⊆ B.
  take (A B : set T) (A_ss_B : A ⊆ B),
  -- By Lemma 1, we have that Ø ⊆ A – B.
  have empty_ss_lhs : ∅ ⊆ A - B, from vacuous_set1a A_ss_B,
  -- By Lemma 2, we have that A – B ⊆ Ø.
  have lhs_ss_empty : A - B ⊆ ∅, from vacuous_set1b A_ss_B,
  -- Thus by our earlier lemma, A – B = Ø as required.
  show A - B ∼ ∅, from iff.elim_right eqv_ss (and.intro lhs_ss_empty empty_ss_lhs)

-- 2.3 Indirect Proofs

/- [KS]
Theorem: If n² is even, then n is even.
Proof: By contradiction; assume that n² is even but that n is odd. Since n is odd, n = 2k + 1 for some integer k.
Therefore n² = (2k + 1)² = 4k² + 4k + 1 = 2(2k² + 2k) + 1. This means that n² is odd, contradicting
the fact that we know that n² is even. We have reached a contradiction, so our assumption must have been wrong. Therefore, if n² is even, n must be even.
-/

theorem n2_even_n_even : ∀ (n:nat), even (n²) → even n :=
  -- By contradiction; assume that n² is even but that n is odd.
  assume n (even_n2 : even (n²)),
  by_contradiction
  (take neven_n : ¬ even n,
   have odd_n : odd n, from or.elim (even_or_odd n) (take even_n, absurd even_n neven_n) (take odd_n, odd_n),
   -- Since n is odd, n = 2k + 1 for some integer k.
   obtain k (n_eq_2k_p1 : n = 2 * k + 1), from odd_n,
   -- Therefore n² = (2k + 1)² = 4k² + 4k + 1 = 2(2k² + 2k) + 1.
   have n2_eq_2l_p1 : n² = 2 * (2 * k² + 2 * k) + 1, from
   calc n² = (2 * k + 1)² : n_eq_2k_p1
        ... = 4*k² + 4*k + 1 : sorry
        ... = 2 * (2 * k² + 2 * k) + 1 : sorry,
   -- This means that n² is odd, contradicting the fact that we know that n² is even.
   have odd_n2 : odd (n²), from exists.intro _ n2_eq_2l_p1,
   not_even_and_odd even_n2 odd_n2)
  -- We have reached a contradiction, so our assumption must have been wrong. Therefore, if n² is even, n must be even.

/- [KS]
You have 2,718 balls and five bins. Prove that you cannot distribute all of
the balls into the bins such that each bin contains an odd number of balls.

Proof: By contradiction; assume that there is a way to distribute all 2,718 balls into five bins such that
each bin has an odd number of balls in it. Consider any such way of distributing the balls, and let the
number of balls in the five bins be a, b, c, d, and e. Write the sum a + b + c + d + e as
((a + b) + (c + d)) + e. Since all five numbers have the same parity, both (a + b) and (c + d) are
even. Since (a + b) and (c + d) have the same parity, ((a + b) + (c + d)) must be even. Then, since
((a + b) + (c + d)) is even, the sum ((a + b) + (c + d)) + e must have the same parity as e. Since e is
odd, this means that sum of the number of balls in the five bins is odd, contradicting the fact that
there are an even number of balls distributed across the bins (2,718). We have reached a contradiction,
so our initial assumption must have been wrong and there is no way to distribute 2,718 balls into
five bins such that each bin has an odd number of balls.
-/

/- [DHS]#DESIGN interesting challenge, since we really start with 5 of something, and only later provide variables for each individual -/

open list

definition list.sum (f : T → nat) (l : list T) : nat := list.foldr (λ x acc, f x + acc ) 0 l

lemma balls_in_bins_verbose : ∀ (a b c d e : nat), odd a → odd b → odd c → odd d → odd e → ¬ (a + b + c + d + e = 2718) :=
  -- By contradiction; assume that there is a way to distribute all 2,718 balls into five bins such that
  -- each bin has an odd number of balls in it.
  -- Consider any such way of distributing the balls, and let the number of balls in the five bins be a, b, c, d, and e.
  take a b c d e,
  assume odd_a odd_b odd_c odd_d odd_e,
  assume sum_2718 : a + b + c + d + e = 2718,
  have div2_2718 : 2718 = 2 * 1359, from rfl,
  have even_sum : even 2718, from exists.intro _ div2_2718,
  -- Write the sum a + b + c + d + e as ((a + b) + (c + d)) + e.
  have sum_parens : a + b + c + d + e = ((a + b) + (c + d)) + e, from sorry,
  -- Since all five numbers have the same parity, both (a + b) and (c + d) are even.
  have even_ab : even (a + b), from same_parity_add_even (or.inr (and.intro odd_a odd_b)),
  have even_cd : even (c + d), from same_parity_add_even (or.inr (and.intro odd_c odd_d)),
  -- Since (a + b) and (c + d) have the same parity, ((a + b) + (c + d)) must be even.
  have even_abcd : even ((a+b)+(c+d)), from same_parity_add_even (or.inl (and.intro even_ab even_cd)),
  -- Then, since ((a + b) + (c + d)) is even, the sum ((a + b) + (c + d)) + e must have the same parity as e.
  have sp_e : same_parity (((a + b) + (c + d)) + e) e, from even_add_sp even_abcd e,
  -- Since e is odd, this means that sum of the number of balls in the five bins is odd,
  have odd_sum : odd (((a + b) + (c + d)) + e), from same_parity_odd2 sp_e odd_e,
  -- contradicting the fact that there are an even number of balls distributed across the bins (2,718).
  not_even_and_odd even_sum (sum_2718 ▸ sum_parens⁻¹ ▸ odd_sum)
  -- We have reached a contradiction, so our initial assumption must have been wrong and there is no way to distribute 2,718 balls into five bins such that each bin has an odd number of balls.

-- Proof by contrapositive

/- [KS]
Theorem: If not Q → not P, then P → Q.
Proof: By contradiction; assume that not Q → not P, but that P → Q is false. Since P → Q is false,
we know that P is true but Q is false. Since Q is false and not Q → not P, we have that P must be
false. But this contradicts the fact that we know that P is true. We have reached a contradiction, so
our initial assumption must have been false. Thus if not Q → not P, then P → Q.
-/

definition neg_imp : ∀ {P Q : Prop}, ¬ (P → Q) → (P ∧ ¬ Q) :=
  take P Q,
  assume n_P_to_Q,
  or.elim (em Q)
          (take q : Q, absurd (λ p:P, q) n_P_to_Q)
          (take nq : ¬ Q,
             (or.elim (em P)
             (take p : P, and.intro p nq)
             (take np : ¬ P, absurd (λ p, absurd p np) n_P_to_Q)))

theorem by_contrapositive : ∀ {P Q : Prop}, (¬ Q → ¬ P) → (P → Q) :=
  take P Q,
  -- By contradiction; assume that not Q → not P, but that P → Q is false.
  assume nQ_to_nP : ¬ Q → ¬ P,
  by_contradiction
    (assume n_P_to_Q : ¬ (P → Q),
     -- Since P → Q is false, we know that P is true but Q is false.
     have p : P, from and.elim_left (neg_imp n_P_to_Q),
     have nq : ¬ Q, from and.elim_right (neg_imp n_P_to_Q),
     -- Since Q is false and not Q → not P, we have that P must be false.
     have np : ¬ P, from nQ_to_nP nq,
     -- But this contradicts the fact that we know that P is true.
     absurd p np)
    -- We have reached a contradiction, so our initial assumption must have been false. Thus if not Q → not P, then P → Q.

/- [KS]
Theorem: If n² is even, then n is even.
Proof: By contrapositive; we prove that if n is odd, then n² is odd. Let n be any odd integer. Since n is odd, n = 2k + 1 for some integer k.
Therefore, n² = (2k + 1)² = 4k² + 4k + 1 = 2(2k² + 2k) + 1. Thus n² is odd.
-/

lemma not_even_then_odd : ∀ {n : nat}, ¬ even n → odd n :=
  take n not_even_n, or.elim (even_or_odd n) (take even_n, absurd even_n not_even_n) (take odd_n, odd_n)

lemma not_odd_then_even : ∀ {n : nat}, ¬ odd n → even n :=
  take n not_odd_n, or.elim (even_or_odd n) (take even_n, even_n) (take odd_n, absurd odd_n not_odd_n)

lemma double_negation : ∀ {P : Prop}, ¬ ¬ P = P := sorry

lemma even_then_not_odd : ∀ {n : nat}, even n → ¬ odd n := sorry
lemma odd_then_not_even : ∀ {n : nat}, odd n → ¬ (even n) := sorry

theorem n2_even_n_even_by_contrapositive : ∀ (n:nat), even (n²) → even n :=
  take n,
  by_contrapositive
    (assume not_even_n : ¬ (even n),
     show ¬ even (n²), from
     have odd_n : odd n, from not_even_then_odd not_even_n,
     obtain k n_eq_2k_p1, from odd_n,
     have n2_eq_2l_p1 : n² = 2 * (2 * k² + 2 * k) + 1, from
       calc n² = (2 * k + 1)² : n_eq_2k_p1
            ... = 4 * k² + 4 * k + 1 : sorry
            ... = 2 * (2 * k² + 2 * k) + 1 : sorry,
     odd_then_not_even (exists.intro _ n2_eq_2l_p1))

-- Chapter 3. Induction

/- [KS]
Theorem: The sum of the first n positive natural numbers is n(n + 1) / 2.
Proof: By induction. Let P(n) be “the sum of the first n positive natural numbers is
n(n + 1) / 2.” We prove that P(n) is true for all n ∈ ℕ, from which the result immediately follows.
For our base case, we prove P(0), that the sum of the first 0 positive natural numbers is
0(0 + 1) / 2. The sum of zero numbers is 0, and 0 = 0(0 + 1)/2. Consequently, P(0) holds.
For the inductive step, assume that for some n ∈ ℕ that P(n) is true and the sum of the first n positive
natural numbers is n(n + 1) / 2. We will prove that P(n + 1) is true; that is, the sum of the first n + 1
positive natural numbers is (n + 1)(n + 2) / 2. Consider the sum of the first n + 1 positive natural
numbers. This is the sum of the first n positive natural numbers, plus n + 1. By our inductive hypothesis,
the sum of the first n positive natural numbers is n(n + 1) / 2. Thus the sum of the first
n + 1 positive natural numbers is
n(n+ 1)/2 + n+ 1
= n(n+ 1)/2 + 2(n+ 1)/2
= (n(n+ 1)+ 2 (n+ 1))/2
= (n+2)(n+ 1)/2
Thus P(n + 1) is true, completing the induction.
-/

definition sum_of_pos : nat → (nat → nat) → nat
| zero f := 0
| (succ n) f := f (n+1) + sum_of_pos n f

theorem sum_of_pos_closed_form : ∀ (n : nat), 2 * sum_of_pos n (λn,n) = n * (n + 1) :=
  take n,
  -- By induction. Let P(n) be “the sum of the first n positive natural numbers is
  -- n(n + 1) / 2.” We prove that P(n) is true for all n ∈ ℕ, from which the result immediately follows.
  nat.induction_on n
                   (-- For our base case, we prove P(0), that the sum of the first 0 positive natural numbers is 0(0 + 1) / 2.
                    -- The sum of zero numbers is 0, and 0 = 0(0 + 1)/2. Consequently, P(0) holds.
                    show 2 * sum_of_pos 0 (λn,n) = 0 * (0 + 1), from sorry)
                  (-- For the inductive step, assume that for some n ∈ ℕ that P(n) is true and the sum of the first n positive natural numbers is n(n + 1) / 2.
                   take n,
                   assume IHn : 2 * sum_of_pos n (λn,n) = n * (n + 1),
                   -- We will prove that P(n + 1) is true; that is, the sum of the first n + 1 positive natural numbers is (n + 1)(n + 2) / 2.
                   show 2 * sum_of_pos (n + 1) (λn,n) = (n + 1) * (n + 2), from
                     -- Consider the sum of the first n + 1 positive natural numbers. This is the sum of the first n positive natural numbers, plus n + 1.
                     calc 2 * sum_of_pos (n + 1) (λn,n) = 2 * (n+1 + sum_of_pos n (λn,n)) : rfl
                          ... = 2 * (n+1) + 2 * sum_of_pos n (λn,n) : sorry
                          -- By our inductive hypothesis, the sum of the first n positive natural numbers is n(n + 1) / 2.
                          ... = 2 * (n+1) + n * (n + 1) : IHn
                          -- [DHS] (Keith's calculations are more cumbersome due to the division)
                          ... = n*n + 3*n + 2 : sorry
                          ... = (n+1)*(n+2) : sorry
                     -- Thus P(n + 1) is true, completing the induction.
                     )

/- [KS]
Theorem: For any natural number n, ∑_{i=0}^{n-1} (2i + 1) = n²
Proof: By induction. Let P(n) be defined as
P(n)≡∑
i=0
n−1
(2i+ 1)=n
2
We prove that P(n) is true for all n ∈ ℕ by induction on n. As our base case, we prove P(0), that
∑_{i=0}^{-1} (2 i+ 1)=0².

The left-hand side of this equality is the empty sum, which is 0. The right-hand side of the equality
is also 0, so P(0) holds.

For the inductive step, assume that for some natural number n, P(n) holds, meaning that
∑_{i=0}^{n−1} (2 i+ 1) = n².

We will prove P(n + 1), meaning that
∑_{i=0}^{n} (2 i+ 1) = (n+1)².
To see this, note that
∑_{i=0}^{n} (2i+ 1)=(n+ 1)²
= ∑_{i=0}^{n−1} (2i+ 1)+ 2 n+ 1
= n² + 2n+ 1
=(n+ 1)²

Thus P(n + 1) holds, completing the induction.
-/

theorem sum_odd_nats : ∀ (n:nat), sum_of_pos n (λ n, 2 * n + 1) = (n+1)² :=
  take n,
  nat.induction_on n
                   (show sum_of_pos 0 (λ n, 2 * n + 1) = (0+1)², from sorry)
                   (assume n (IHn : sum_of_pos n (λ n, 2*n+1) = (n+1)²),
                    show sum_of_pos (n+1) (λ n,2*n+1) = (n+2)², from
                      calc sum_of_pos (n+1) (λ n,2*n+1) = 2 * (n+1) + 1 + sum_of_pos n (λ n,2*n+1) : rfl
                           ... = 2 * (n+1) + 1 + (n+1)² : sorry
                           ... = n² + 4*n + 4 : sorry
                           ... = (n+2)² : sorry)

/- [KS]
Theorem: For any natural number n, ∑_{i=0}^{n−1} i = n.
Proof: <too many annoying symbols>
-/
definition constant_fn (i : nat) : nat → nat := λ n, i

theorem sum_constant : ∀ (n:nat), sum_of_pos n (constant_fn 1) = n :=
  take n,
  nat.induction_on n
                   (show sum_of_pos 0 (constant_fn 1) = 0, from rfl)
                   (assume n (IHn : sum_of_pos n (constant_fn 1) = n),
                    show sum_of_pos (n+1) (constant_fn 1) = n+1, from
                      calc sum_of_pos (n+1) (constant_fn 1) = 1 + sum_of_pos n (constant_fn 1) : rfl
                           ... = 1 + n : IHn
                           ... = n + 1 : add_comm)
-- Variants on Induction

/- [KS]
Theorem: Let P(n) be a property that applies to natural numbers and let k be a natural number. If
the following are true:
- P(k) is true
- For any n ∈ ℕ with n ≥ k, P(n) → P(n + 1)
Then for any n ∈ ℕ with n ≥ k, P(n) is true.

Proof: Consider any property P(n) of the natural numbers for which the above is true. Now, define
Q(n) ≡ P(n + k). This proof will work in two parts: first, we will prove, by induction, that Q(n) is
true for all n . Next, we will show that this implies that ∈ ℕ P(n) is true for all n where ∈ ℕ n ≥ k.
First, we prove that Q(n) is true for all n by induction. As our base case, we prove ∈ ℕ Q(0),
meaning that P(k) holds. By our choice of P, we know that this is true. Thus Q(0) holds.
For the inductive step, assume that for some n that ∈ ℕ Q(n) is true, meaning that P(n + k) holds.
We will prove that Q(n + 1) is true, meaning that P(n + k + 1) holds. Now, since n ∈ ℕ , we know
that n ≥ 0. Consequently, we know that n + k ≥ k. Thus by the properties of P, we know that
P(n + k) implies P(n + k + 1). Since Q(n + 1) ≡ P(n + k + 1), this proves that Q(n + 1) holds, completing
the induction. Thus Q(n) is true for all natural numbers n.
Now, we use this result to prove that P(n) is true for all natural numbers n ≥ k. Consider any arbitrary
natural number n ≥ k. Thus n – k ≥ 0, so n – k is a natural number. Therefore, Q(n – k) holds.
Since (n – k) + k = n, this means that P(n) holds. Since our choice of n was arbitrary, this shows
that P(n) is true for all natural numbers n ≥ k.
-/

-- [DHS]#TODO tricky to avoid burdens, need the elaborator to kick-ass mod AC

open nat

definition shift (P : nat → Prop) (k : nat) : nat → Prop := λ m, P (m + k)

theorem nat.late_induction_on {P : nat → Prop} (k : nat) (base : P k) (step : ∀ (n:nat), n ≥ k → P n → P (n+1)) : ∀ (n:nat), n ≥ k → P n :=
  -- Consider any property P(n) of the natural numbers for which the above is true.
  -- Now, define Q(n) ≡ P(n + k).
  let Q := shift P k in
  let Q := λ n, P (n + k) in
  -- This proof will work in two parts: first, we will prove, by induction, that Q(n) is true for all n.
  -- Next, we will show that this implies that ∈ ℕ P(n) is true for all n where ∈ ℕ n ≥ k.

-- First, we prove that Q(n) is true for all n
  have Q_holds : ∀ n, Q n, from
    take n,
  -- by induction.
    nat.induction_on n
                     (--  As our base case, we prove ∈ ℕ Q(0), meaning that P(k) holds.
                     show Q 0, from
                     -- By our choice of P, we know that this is true. Thus Q(0) holds.
                     (zero_add k)⁻¹ ▸ base)
                     (-- For the inductive step, assume that for some n that ∈ ℕ Q(n) is true,
                     (assume n (Qn : Q n),
                      -- meaning that P(n + k) holds.
                      have pnk : P (n + k), from Qn,
                      -- We will prove that Q(n + 1) is true, meaning that P(n + k + 1) holds.
                      show Q (n+1), from
                      -- Now, since n ∈ ℕ , we know that n ≥ 0. Consequently, we know that n + k ≥ k.
                      have npk_ge_k : n + k ≥ k, from le_add_left k n,
                      -- Thus by the properties of P, we know that P(n + k) implies P(n + k + 1).
                      have pnk_p1 : P (n + k + 1), from step (n+k) npk_ge_k Qn,
                      -- Since Q(n + 1) ≡ P(n + k + 1), this proves that Q(n + 1) holds, completing the induction.
                      (add.right_comm n k 1) ▸ pnk_p1
                      -- Thus Q(n) is true for all natural numbers n.
                      )
  -- Now, we use this result to prove that P(n) is true for all natural numbers n ≥ k.
  show ∀ (n : nat), n ≥ k → P n, from
  -- Consider any arbitrary natural number n ≥ k.
  take (n : nat) (n_ge_k : n ≥ k),
  -- Thus n – k ≥ 0, so n – k is a natural number.
  -- Therefore, Q(n – k) holds. Since (n – k) + k = n, this means that P(n) holds.
  have n_mk_pk : (n - k) + k = n, from sub_add_cancel n_ge_k,
  have Q_nmk : Q (n - k), from Q_holds (n-k),
  n_mk_pk ▸ Q_nmk
  -- Since our choice of n was arbitrary, this shows that P(n) is true for all natural numbers n ≥ k.


-- Monotonía de la multiplicación por no negativo
-- ==============================================

-- Demostrar que si a, b y c son números reales tales que
-- 0 ≤ c y a ≤ b, entonces a*c ≤ b*c.

import data.real.basic

variables {a b c : ℝ}

-- 1ª demostración
example
  (hc : 0 ≤ c)
  (hab : a ≤ b)
  : a * c ≤ b * c :=
begin
  rw ← sub_nonneg,
  have h : b * c - a * c = (b - a) * c,
  { ring, },
  { rw h,
    apply mul_nonneg,
    { rw sub_nonneg,
      exact hab, },
    { exact hc, }},
end

-- 2ª demostración
example
  (hc : 0 ≤ c)
  (hab : a ≤ b)
  : a * c ≤ b * c :=
begin
  have hab' : 0 ≤ b - a,
  { rw ← sub_nonneg at hab,
    exact hab, },
  have h1 : 0 ≤ (b - a) * c,
  { exact mul_nonneg hab' hc, },
  have h2 : (b - a) * c = b * c - a * c,
  { ring, },
  have h3 : 0 ≤ b * c - a * c,
  { rw h2 at h1,
    exact h1, },
  rw sub_nonneg at h3,
  exact h3,
end

-- 3ª demostración
example
  (hc : 0 ≤ c)
  (hab : a ≤ b)
  : a * c ≤ b * c :=
begin
  have hab' : 0 ≤ b - a,
  { rwa ← sub_nonneg at hab, },
  have h1 : 0 ≤ (b - a) * c,
  { exact mul_nonneg hab' hc, },
  have h2 : (b - a) * c = b * c - a * c,
  { ring, },
  have h3 : 0 ≤ b * c - a * c,
  { rwa h2 at h1, },
  rwa sub_nonneg at h3,
end

-- 4ª demostración
example
  (hc : 0 ≤ c)
  (hab : a ≤ b)
  : a * c ≤ b * c :=
begin
  rw ← sub_nonneg,
  calc 0 ≤ (b - a)*c  : mul_nonneg (by rwa sub_nonneg) hc
     ... =  b*c - a*c : by ring,
end

-- 5ª demostración
example
  (hc : 0 ≤ c)
  (hab : a ≤ b)
  : a * c ≤ b * c :=
mul_mono_nonneg hc hab

-- 6ª demostración
example
  (hc : 0 ≤ c)
  (hab : a ≤ b)
  : a * c ≤ b * c :=
by nlinarith


/-
Conjunctions, disjunctions, implication, iff,
negation
-/

/-
1. Prove that 3 + 3 = 6 and 2 + 6 = 8 implies
that 1 + 1 = 2.
-/

-- answer:

/-
2. Prove that 2 + 5 = 3 or 9 + 1 = 5 implies
that 2 + 3 = 9.
-/

-- answer:

/-
3. Prove that ¬(A ∧ B ∧ C) ↔ (¬A ∨ ¬B ∨ ¬C).
You may use the axiom of the excluded middle.
-/

axiom em: ∀(P: Prop), P ∨ ¬P

-- answer:

/-
4. Prove that ¬(A ∨ B ∨ C) ↔ (¬A ∧ ¬B ∧ ¬C).
You may *NOT* use the axiom of the excluded middle.
-/

-- answer:

/-
5a. Prove that ¬(A ∨ ¬B) ↔ (¬A ∧ B).
You may use the axiom of the excluded middle.
-/

-- answer:

/-
5b. Prove that ¬(A ∧ ¬B) ↔ (¬A ∨ B).
You may use the axiom of the excluded middle.
-/

-- answer:

/-
6. Prove that ¬P ∨ ¬Q ∨ R is true if and only
if P → Q → R. You may use the axiom of the
excluded middle.
-/

-- answer:

/-
7. Prove that ¬¬¬P → ¬P. You may use the axiom
of the excluded middle.
-/

-- answer:

/-
Universal Quantifiers, Existential Quantifiers, and
Satisfiability.
-/

/-
8. Prove the following statements are satisfiable
or prove that they are not. You may use the axiom
of the excluded middle to prove cases where the
statements are not satisfiable.
-/

/-
8a. (P ∨ Q) ∧ (¬P ∨ Q) ∧ (P ∨ ¬Q)
-/

-- answer:

/-
8b. (P ∨ Q) ∧ (¬P ∨ Q) ∧ (P ∨ ¬Q) ∧ (¬P ∨ ¬Q)
-/

-- answer:

/-
8c. (P ∨ Q ∨ R) ∧ (¬P ∨ Q ∨ R) ∧
     (P ∨ ¬Q ∨ ¬R) ∧ (¬P ∨ ¬Q ∨ ¬R)
-/

-- answer:

/-
9. Prove that exists a number such that
it is both even and a multiple of 3.
Use the supplied definitions of even and prime.
-/

def isEven(n: ℕ) := (∃(m: ℕ), m * 2 = n)

def isMult3(n: ℕ) := (∃(m: ℕ), m * 3 = n)

-- answer

/-
10a. Write the lemma that if there exists
 someone that you can fool all of the time,
 then there always exists someone you can fool.
 Use the supplied axioms, and make sure you use
 at least as many parentheses as needed. (It's
 okay to use more than you need.)
10b. Prove the above lemma.
-/

axioms Person Time: Type
axiom fool: Person → Time → Prop

-- answers:


import .love06_monads_demo

/- # LoVe Homework 6: Monads

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

/- ## Question 1 (6 points): Better Exceptions

The __error monad__ is a monad stores either a value of type `α` or an error of
type `ε`. This corresponds to the following type: -/

inductive error (ε α : Type) : Type
| good {} : α → error
| bad {}  : ε → error

/- The error monad generalizes the option monad seen in the lecture. The `good`
constructor, corresponding to `option.some`, stores the current result of the
computation. But instead of having a single bad state `option.none`, the error
monad has many bad states of the form `bad e`, where `e` is an "exception" of
type `ε`.

1.1 (1 point). Implement a variant of `list.nth` that returns an error
message of the form "index _i_ out of range" instead of `option.none` on
failure.

Hint: For this, you will only need pattern matching (no monadic code). -/

#check list.nth

def list.nth_error {α : Type} (as : list α) (i : ℕ) : error string α :=
sorry

/- 1.2 (1 point). Complete the definitions of the `pure` and `bind` operations
on the error monad: -/

def error.pure {ε α : Type} : α → error ε α :=
sorry

def error.bind {ε α β : Type} : error ε α → (α → error ε β) → error ε β :=
sorry

/- The following type class instance makes it possible to use `>>=` and `do`
notations in conjunction with error monads: -/

@[instance] def error.monad {ε : Type} : monad (error ε) :=
{ pure := @error.pure ε,
  bind := @error.bind ε }

/- 1.3 (2 point). Prove the monad laws for the error monad. -/

lemma error.pure_bind {ε α β : Type} (a : α) (f : α → error ε β) :
  (pure a >>= f) = f a :=
sorry

lemma error.bind_pure {ε α : Type} (ma : error ε α) :
  (ma >>= pure) = ma :=
sorry

lemma error.bind_assoc {ε α β γ : Type} (f : α → error ε β) (g : β → error ε γ)
    (ma : error ε α) :
  ((ma >>= f) >>= g) = (ma >>= (λa, f a >>= g)) :=
sorry

/- 1.4 (1 point). Define the following two operations on the error monad.

The `throw` operation raises an exception `e`, leaving the monad in a bad state
storing `e`.

The `catch` operation can be used to recover from an earlier exception. If the
monad is currently in a bad state storing `e`, `catch` invokes some
exception-handling code (the second argument to `catch`), passing `e` as
argument; this code might in turn raise a new exception. If `catch` is applied
to a good state, nothing happens—the monad remains in the good state. As a
convenient alternative to `error.catch ma g`, Lean lets us write
`ma.catch g`. -/

def error.throw {ε α : Type} : ε → error ε α :=
sorry

def error.catch {ε α : Type} : error ε α → (ε → error ε α) → error ε α :=
sorry

/- 1.5 (1 point). Using `list.nth_error` and the monadic operations on `error`,
and the special `error.catch` operation, write a monadic program that swaps the
values at indexes `i` and `j` in the input list `as`. If either of the indices
is out of range, return `as` unchanged. -/

def list.swap {α : Type} (as : list α) (i j : ℕ) : error string (list α) :=
sorry

#reduce list.swap [3, 1, 4, 1] 0 2   -- expected: error.good [4, 1, 3, 1]
#reduce list.swap [3, 1, 4, 1] 0 7   -- expected: error.good [3, 1, 4, 1]

/- ## Question 2 (3 points + 1 bonus point): Properties of `mmap`

We will prove some properties of the `mmap` function introduced in the
lecture's demo. -/

#check mmap

/- 2.1 (1 point). Prove the following identity law about `mmap` for an
arbitrary monad `m`.

Hint: You will need the lemma `lawful_monad.pure_bind` in the induction step. -/

lemma mmap_pure {m : Type → Type} [lawful_monad m] {α : Type} (as : list α) :
  mmap (@pure m _ _) as = pure as :=
sorry

/- Commutative monads are monads for which we can reorder actions that do not
depend on each others. Formally: -/

@[class] structure comm_lawful_monad (m : Type → Type)
  extends lawful_monad m : Type 1 :=
(bind_comm {α β γ δ : Type} (ma : m α) (f : α → m β) (g : α → m γ)
     (h : α → β → γ → m δ) :
   (ma >>= (λa, f a >>= (λb, g a >>= (λc, h a b c)))) =
   (ma >>= (λa, g a >>= (λc, f a >>= (λb, h a b c)))))

/- 2.2 (1 point). Prove that `option` is a commutative monad. -/

lemma option.bind_comm {α β γ δ : Type} (ma : option α) (f : α → option β)
    (g : α → option γ) (h : α → β → γ → option δ) :
  (ma >>= λa, f a >>= λb, g a >>= λc, h a b c) =
  (ma >>= λa, g a >>= λc, f a >>= λb, h a b c) :=
sorry

/- 2.3 (1 point). Explain why `error` is not a commutative monad. -/

-- enter your answer here

/- 2.4 (1 bonus point). Prove the following composition law for `mmap`, which
holds for commutative monads.

Hint: You will need structural induction. -/

lemma mmap_mmap {m : Type → Type} [comm_lawful_monad m]
    {α β γ : Type} (f : α → m β) (g : β → m γ) (as : list α) :
  (mmap f as >>= mmap g) = mmap (λa, f a >>= g) as :=
sorry

end LoVe


import Std.Data.List.Lemmas
import Mathlib.Data.List.Basic

/-

Homework

To get comfortable with predicate logic, this assignement will
investigate relations a little.

We first declare a type `α` and a relation `R` on it.

-/

variable {α : Type} (R : α → α → Prop)

/-

There are many different properties one can expect of `R`.
Here are some common ones.

-/

def reflexive : Prop := ∀ a, R a a

def antisymmetric : Prop := ∀ a b, R a b → R b a → a = b

def transitive : Prop :=  ∀ a b c, R a b → R b c → R a c

/-

Let's give an example of reflexive, antisymmetric, and
transitive relation on basic type: containment of strings.

Strings are constructed from `List Char`. To get the
underlying list on `s : String`you can use `s.data` (or
`s.toList`).

We will bootstrap off `List.Sublist` which is already
proven to be reflexive, antisymmetric, and transitive.

-/

/-- `String.Subset s t` asks that `s` is a contiguous
substring of `t`-/
def String.Subset (s t : String) : Prop :=
  List.Sublist s.data t.data

/- We will learn more about instances later. Right
now these two instances allow us to use the `⊆` notation
and all `eval` to compute `s ⊆ t`.
-/
instance : HasSubset String where
  Subset := String.Subset

instance decSubset (s t : String) : Decidable (s ⊆ t) := by
  change Decidable (List.Sublist s.data t.data)
  exact inferInstance

-- It works as expected.
#eval "my" ⊆ "my doggie"
#eval "dog" ⊆ "my doggie"
#eval "cat" ⊆ "my doggie"

-- `⊆` is reflexive
theorem String.Subset_refl (s : String) : s ⊆ s := by
  apply List.Sublist.refl

-- `⊆` is antisymmetric
theorem String.Subset_antisym (s t : String) (h : s ⊆ t) (h' : t ⊆ s) : s = t := by
  have : s.data = t.data := by
    apply List.Sublist.antisymm
    repeat assumption
  exact congrArg String.mk this

-- `⊆` is transitive.
theorem String.Subset_trans (u v w : String) (huv : u ⊆ v) (hvw : v ⊆ w) : u ⊆ w := by
  apply List.Sublist.trans
  repeat assumption

/-
A reflexive, antisymmetric, and transitive relation is a called a _partial order_.
Other examples of partial orders are `≤` on `ℝ` or `⊆` on `𝒫 X` where `X` is a set.

Let's prove some basic properties about partial orders.
-/

structure partialOrder {α : Type} (R : α → α → Prop) where
  refl : reflexive R
  antisym : antisymmetric R
  trans : transitive R

/-
The first three problems get you used to extracting data from a structure.

The fourth asks to you construct a structure from terms of its fields.
-/

theorem prob01 (h : partialOrder R) : reflexive R := sorry

theorem prob02 (h : partialOrder R) : antisymmetric R := sorry

theorem prob03 (h : partialOrder R) : transitive R := sorry

theorem prob04 (hrfl : reflexive R) (hasym : antisymmetric R) (htrans : transitive R) :
    partialOrder R := sorry

/-
An _infimum_ of `R` is a term `a₀` that is "below" all other ones for `R`.
-/

def Inf (a₀ : α) : Prop := ∀ a, R a₀ a

/- Prove that infimums are unique if `R` is a partial order -/
theorem prob05 (h : partialOrder R) (a₁ a₂ : α) (h₁ : Inf R a₁) (h₂ : Inf R a₂)
    : a₁ = a₂ := sorry

/- Bonus: does string containment have an infimum? If so can you prove it? -/

/- We can `reverse` a relation by exchanging the two entries -/
def reverse (S : α → α → Prop) : α → α → Prop := fun a a' => S a' a

/- Reversing a partial order gives another partial order -/
theorem prob06 (h : partialOrder R) : partialOrder $ reverse R := sorry


/-
Copyright (c) 2020 The Xena project. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Buzzard.
Thanks: Imperial College London, leanprover-community
-/

-- Import levels 1 to 3
import complex.kb_solutions.Level_03_conj

/-!

# Level 4: Norms

Define `norm_sq : ℂ → ℝ` by defining `norm_sq(z)` to be
`re(z)*re(z)+im(z)*im(z)` and see what you can prove about it.

-/

namespace complex

/-- The real number which is the squared norm of z -/
def norm_sq (z : ℂ) : ℝ := re(z)*re(z)+im(z)*im(z)

local attribute [simp] ext_iff

-- next few proofs are are all mostl simp [norm_sq] so let's locally
-- tell simp about it for this section only

section simp_knows_norm_sq

local attribute [simp] norm_sq
/-! ## Behaviour with respect to 0, 1 and I -/

@[simp] lemma norm_sq_zero : norm_sq 0 = 0 := by simp

@[simp] lemma norm_sq_one : norm_sq 1 = 1 := by simp
@[simp] lemma norm_sq_I : norm_sq I = 1 := by simp

/-! ## Behaviour with respect to *, + and - -/

@[simp] lemma norm_sq_mul (z w : ℂ) : norm_sq (z * w) = norm_sq z * norm_sq w :=
by simp; ring

lemma norm_sq_add (z w : ℂ) : norm_sq (z + w) =
  norm_sq z + norm_sq w + 2 * (z * conj w).re :=
by simp; ring

@[simp] lemma norm_sq_neg (z : ℂ) : norm_sq (-z) = norm_sq z :=
by simp

/-! ## Behaviour with respect to `conj` -/

@[simp] lemma norm_sq_conj (z : ℂ) : norm_sq (conj z) = norm_sq z :=
by simp

/-! ## Behaviour with respect to real numbers` -/

@[simp] lemma norm_sq_of_real (r : ℝ) : norm_sq r = r * r :=
by simp

theorem mul_conj (z : ℂ) : z * conj z = norm_sq z :=
by simp; ring

-- time to end the section; simp no longer knows about norm_sq

end simp_knows_norm_sq

/-! ## Behaviour with respect to real 0, ≤, < and so on -/

-- Warning: you will have to know something about Lean's API for
-- real numbers to solve these ones. If you turn the statements about
-- complex numbers into statements about real numbers, you'll find
-- they're of the form "prove $$x^2+y^2\geq0$$" with `x` and `y` real.

example (y : ℝ) : 0 ≤ y * y := mul_self_nonneg y

namespace realtac

lemma norm_nonneg (x y : ℝ) : 0 ≤ x * x + y * y :=
begin
  nlinarith,
end

lemma norm_eq_zero_iff {x y : ℝ} : x * x + y * y = 0 ↔ x = 0 ∧ y = 0 :=
begin
  split,
  { intro h,
    rw add_eq_zero_iff' at h,
    { simp * at *},
    { apply mul_self_nonneg},
    { apply mul_self_nonneg}},
  { rintros ⟨rfl, rfl⟩,
    simp,
  }
end

lemma norm_nonpos_right {x y : ℝ} (h1 : x * x + y * y ≤ 0) : y = 0 :=
begin
  suffices : x = 0 ∧ y = 0,
    simp [this],
  rw ←norm_eq_zero_iff,
  linarith [norm_nonneg x y],
end

lemma norm_nonpos_left (x y : ℝ) (h1 : x * x + y * y ≤ 0) : x = 0 :=
begin
  rw add_comm at h1,
  apply norm_nonpos_right h1,
end

end realtac

-- back to the levels

lemma norm_sq_nonneg (z : ℂ) : 0 ≤ norm_sq z :=
begin
  cases z with x y,
  simp [norm_sq],
  apply realtac.norm_nonneg,
end

@[simp] lemma norm_sq_eq_zero (z : ℂ) : norm_sq z = 0 ↔ z = 0 :=
begin
  cases z with x y,
  simp [norm_sq],
  exact realtac.norm_eq_zero_iff
end

@[simp] lemma norm_sq_ne_zero {z : ℂ} : norm_sq z ≠ 0 ↔ z ≠ 0 :=
begin
  have h := norm_sq_eq_zero z,
  tauto!
end

@[simp] lemma norm_sq_pos {z : ℂ} : 0 < norm_sq z ↔ z ≠ 0 :=
begin
  rw lt_iff_le_and_ne,
  rw ne_comm,
  simp [norm_sq_nonneg],
end

lemma re_sq_le_norm_sq (z : ℂ) : z.re * z.re ≤ norm_sq z :=
begin
  simp [norm_sq],
  apply mul_self_nonneg,
end

lemma im_sq_le_norm_sq (z : ℂ) : z.im * z.im ≤ norm_sq z :=
begin
  simp [norm_sq],
  apply mul_self_nonneg
end

end complex


/-
Copyright (c) 2022 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Damiano Testa

! This file was ported from Lean 3 source module algebra.parity
! leanprover-community/mathlib commit 8631e2d5ea77f6c13054d9151d82b83069680cb1
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.GroupPower.Lemmas
import Mathlib.Data.Nat.Cast.Basic

/-!  # Squares, even and odd elements

This file proves some general facts about squares, even and odd elements of semirings.

In the implementation, we define `IsSquare` and we let `Even` be the notion transported by
`to_additive`.  The definition are therefore as follows:
```lean
IsSquare a ↔ ∃ r, a = r * r
Even a ↔ ∃ r, a = r + r
```

Odd elements are not unified with a multiplicative notion.

## Future work

* TODO: Try to generalize further the typeclass assumptions on `IsSquare/even`.
  For instance, in some cases, there are `Semiring` assumptions that I (DT) am not convinced are
  necessary.
* TODO: Consider moving the definition and lemmas about `odd` to a separate file.
* TODO: The "old" definition of `Even a` asked for the existence of an element `c` such that
  `a = 2 * c`.  For this reason, several fixes introduce an extra `two_mul` or `← two_mul`.
  It might be the case that by making a careful choice of `simp` lemma, this can be avoided.
 -/

open MulOpposite

variable {F α β R : Type _}

section Mul

variable [Mul α]

/-- An element `a` of a type `α` with multiplication satisfies `IsSquare a` if `a = r * r`,
for some `r : α`. -/
@[to_additive
      "An element `a` of a type `α` with addition satisfies `Even a` if `a = r + r`,
      for some `r : α`."]
def IsSquare (a : α) : Prop :=
  ∃ r, a = r * r
#align is_square IsSquare
#align even Even

@[to_additive (attr := simp)]
theorem isSquare_mul_self (m : α) : IsSquare (m * m) :=
  ⟨m, rfl⟩
#align is_square_mul_self isSquare_mul_self
#align even_add_self even_add_self

@[to_additive]
theorem isSquare_op_iff (a : α) : IsSquare (op a) ↔ IsSquare a :=
  ⟨fun ⟨c, hc⟩ => ⟨unop c, by rw [← unop_mul, ← hc, unop_op]⟩, fun ⟨c, hc⟩ => by simp [hc]⟩
#align is_square_op_iff isSquare_op_iff
#align even_op_iff even_op_iff

end Mul

@[to_additive (attr := simp)]
theorem isSquare_one [MulOneClass α] : IsSquare (1 : α) :=
  ⟨1, (mul_one _).symm⟩
#align is_square_one isSquare_one
#align even_zero even_zero

@[to_additive]
theorem IsSquare.map [MulOneClass α] [MulOneClass β] [MonoidHomClass F α β] {m : α} (f : F) :
    IsSquare m → IsSquare (f m) := by
  rintro ⟨m, rfl⟩
  exact ⟨f m, by simp⟩
#align is_square.map IsSquare.map
#align even.map Even.map

section Monoid

variable [Monoid α] {n : ℕ} {a : α}

@[to_additive even_iff_exists_two_nsmul]
theorem isSquare_iff_exists_sq (m : α) : IsSquare m ↔ ∃ c, m = c ^ 2 := by simp [IsSquare, pow_two]
#align is_square_iff_exists_sq isSquare_iff_exists_sq
#align even_iff_exists_two_nsmul even_iff_exists_two_nsmul

alias isSquare_iff_exists_sq ↔ IsSquare.exists_sq isSquare_of_exists_sq
#align is_square.exists_sq IsSquare.exists_sq
#align is_square_of_exists_sq isSquare_of_exists_sq

attribute
  [to_additive Even.exists_two_nsmul
      "Alias of the forwards direction of `even_iff_exists_two_nsmul`."]
  IsSquare.exists_sq
#align even.exists_two_nsmul Even.exists_two_nsmul

@[to_additive]
theorem IsSquare.pow (n : ℕ) : IsSquare a → IsSquare (a ^ n) := by
  rintro ⟨a, rfl⟩
  exact ⟨a ^ n, (Commute.refl _).mul_pow _⟩
#align is_square.pow IsSquare.pow
#align even.nsmul Even.nsmul

/- Porting note: `simp` attribute removed because linter reports:
simp can prove this:
  by simp only [even_two, Even.nsmul']
-/
@[to_additive Even.nsmul']
theorem Even.isSquare_pow : Even n → ∀ a : α, IsSquare (a ^ n) := by
  rintro ⟨n, rfl⟩ a
  exact ⟨a ^ n, pow_add _ _ _⟩
#align even.is_square_pow Even.isSquare_pow
#align even.nsmul' Even.nsmul'

/- Porting note: `simp` attribute removed because linter reports:
simp can prove this:
  by simp only [even_two, Even.is_square_pow]
-/
@[to_additive even_two_nsmul]
theorem IsSquare_sq (a : α) : IsSquare (a ^ 2) :=
  ⟨a, pow_two _⟩
#align is_square_sq IsSquare_sq
#align even_two_nsmul even_two_nsmul

variable [HasDistribNeg α]

theorem Even.neg_pow : Even n → ∀ a : α, (-a) ^ n = a ^ n := by
  rintro ⟨c, rfl⟩ a
  simp_rw [← two_mul, pow_mul, neg_sq]
#align even.neg_pow Even.neg_pow

theorem Even.neg_one_pow (h : Even n) : (-1 : α) ^ n = 1 := by rw [h.neg_pow, one_pow]
#align even.neg_one_pow Even.neg_one_pow

end Monoid

@[to_additive]
theorem IsSquare.mul [CommSemigroup α] {a b : α} : IsSquare a → IsSquare b → IsSquare (a * b) := by
  rintro ⟨a, rfl⟩ ⟨b, rfl⟩
  exact ⟨a * b, mul_mul_mul_comm _ _ _ _⟩
#align is_square.mul IsSquare.mul
#align even.add Even.add

variable (α)

@[simp]
theorem isSquare_zero [MulZeroClass α] : IsSquare (0 : α) :=
  ⟨0, (mul_zero _).symm⟩
#align is_square_zero isSquare_zero

variable {α}

section DivisionMonoid

variable [DivisionMonoid α] {a : α}

@[to_additive (attr := simp)]
theorem isSquare_inv : IsSquare a⁻¹ ↔ IsSquare a := by
  refine' ⟨fun h => _, fun h => _⟩
  · rw [← isSquare_op_iff, ← inv_inv a]
    exact h.map (MulEquiv.inv' α)
  · exact ((isSquare_op_iff a).mpr h).map (MulEquiv.inv' α).symm
#align is_square_inv isSquare_inv
#align even_neg even_neg

alias isSquare_inv ↔ _ IsSquare.inv
#align is_square.inv IsSquare.inv

attribute [to_additive] IsSquare.inv
#align even.neg Even.neg

@[to_additive]
theorem IsSquare.zpow (n : ℤ) : IsSquare a → IsSquare (a ^ n) := by
  rintro ⟨a, rfl⟩
  exact ⟨a ^ n, (Commute.refl _).mul_zpow _⟩
#align is_square.zpow IsSquare.zpow
#align even.zsmul Even.zsmul

variable [HasDistribNeg α] {n : ℤ}

theorem Even.neg_zpow : Even n → ∀ a : α, (-a) ^ n = a ^ n := by
  rintro ⟨c, rfl⟩ a
  exact zpow_bit0_neg _ _
#align even.neg_zpow Even.neg_zpow

theorem Even.neg_one_zpow (h : Even n) : (-1 : α) ^ n = 1 := by rw [h.neg_zpow, one_zpow]
#align even.neg_one_zpow Even.neg_one_zpow

end DivisionMonoid

theorem even_abs [SubtractionMonoid α] [LinearOrder α] {a : α} : Even (|a|) ↔ Even a := by
  cases abs_choice a
  · have h : abs a = a := by assumption
    simp only [h, even_neg]
  · have h : abs a = -a := by assumption
    simp only [h, even_neg]
#align even_abs even_abs

@[to_additive]
theorem IsSquare.div [DivisionCommMonoid α] {a b : α} (ha : IsSquare a) (hb : IsSquare b) :
    IsSquare (a / b) := by
  rw [div_eq_mul_inv]
  exact ha.mul hb.inv
#align is_square.div IsSquare.div
#align even.sub Even.sub

@[to_additive (attr := simp) Even.zsmul']
theorem Even.isSquare_zpow [Group α] {n : ℤ} : Even n → ∀ a : α, IsSquare (a ^ n) := by
  rintro ⟨n, rfl⟩ a
  exact ⟨a ^ n, zpow_add _ _ _⟩
#align even.is_square_zpow Even.isSquare_zpow
#align even.zsmul' Even.zsmul'

-- `odd.tsub` requires `CanonicallyLinearOrderedSemiring`, which we don't have
theorem Even.tsub [CanonicallyLinearOrderedAddMonoid α] [Sub α] [OrderedSub α]
    [ContravariantClass α α (· + ·) (· ≤ ·)] {m n : α} (hm : Even m) (hn : Even n) :
    Even (m - n) := by
  obtain ⟨a, rfl⟩ := hm
  obtain ⟨b, rfl⟩ := hn
  refine' ⟨a - b, _⟩
  obtain h | h := le_total a b
  · rw [tsub_eq_zero_of_le h, tsub_eq_zero_of_le (add_le_add h h), add_zero]
  · exact (tsub_add_tsub_comm h h).symm
#align even.tsub Even.tsub

set_option linter.deprecated false in
theorem even_iff_exists_bit0 [Add α] {a : α} : Even a ↔ ∃ b, a = bit0 b :=
  Iff.rfl
#align even_iff_exists_bit0 even_iff_exists_bit0

alias even_iff_exists_bit0 ↔ Even.exists_bit0 _
#align even.exists_bit0 Even.exists_bit0

section Semiring

variable [Semiring α] [Semiring β] {m n : α}

theorem even_iff_exists_two_mul (m : α) : Even m ↔ ∃ c, m = 2 * c := by
  simp [even_iff_exists_two_nsmul]
#align even_iff_exists_two_mul even_iff_exists_two_mul

theorem even_iff_two_dvd {a : α} : Even a ↔ 2 ∣ a := by simp [Even, Dvd.dvd, two_mul]
#align even_iff_two_dvd even_iff_two_dvd

alias even_iff_two_dvd ↔ Even.two_dvd _
#align even.two_dvd Even.two_dvd

theorem Even.trans_dvd (hm : Even m) (hn : m ∣ n) : Even n :=
  even_iff_two_dvd.2 <| hm.two_dvd.trans hn
#align even.trans_dvd Even.trans_dvd

theorem Dvd.dvd.even (hn : m ∣ n) (hm : Even m) : Even n :=
  hm.trans_dvd hn
#align has_dvd.dvd.even Dvd.dvd.even

@[simp]
theorem range_two_mul (α) [Semiring α] : (Set.range fun x : α => 2 * x) = { a | Even a } := by
  ext x
  simp [eq_comm, two_mul, Even]
#align range_two_mul range_two_mul

set_option linter.deprecated false in
@[simp] theorem even_bit0 (a : α) : Even (bit0 a) :=
  ⟨a, rfl⟩
#align even_bit0 even_bit0

@[simp]
theorem even_two : Even (2 : α) :=
  ⟨1, by rw[one_add_one_eq_two]⟩
#align even_two even_two

@[simp]
theorem Even.mul_left (hm : Even m) (n) : Even (n * m) :=
  hm.map (AddMonoidHom.mulLeft n)
#align even.mul_left Even.mul_left

@[simp]
theorem Even.mul_right (hm : Even m) (n) : Even (m * n) :=
  hm.map (AddMonoidHom.mulRight n)
#align even.mul_right Even.mul_right

theorem even_two_mul (m : α) : Even (2 * m) :=
  ⟨m, two_mul _⟩
#align even_two_mul even_two_mul

theorem Even.pow_of_ne_zero (hm : Even m) : ∀ {a : ℕ}, a ≠ 0 → Even (m ^ a)
  | 0, a0 => (a0 rfl).elim
  | a + 1, _ => by
    rw [pow_succ]
    exact hm.mul_right _
#align even.pow_of_ne_zero Even.pow_of_ne_zero

section WithOdd

/-- An element `a` of a semiring is odd if there exists `k` such `a = 2*k + 1`. -/
def Odd (a : α) : Prop :=
  ∃ k, a = 2 * k + 1
#align odd Odd

set_option linter.deprecated false in
theorem odd_iff_exists_bit1 {a : α} : Odd a ↔ ∃ b, a = bit1 b :=
  exists_congr fun b => by
    rw [two_mul]
    rfl
#align odd_iff_exists_bit1 odd_iff_exists_bit1

alias odd_iff_exists_bit1 ↔ Odd.exists_bit1 _
#align odd.exists_bit1 Odd.exists_bit1

set_option linter.deprecated false in
@[simp] theorem odd_bit1 (a : α) : Odd (bit1 a) :=
  odd_iff_exists_bit1.2 ⟨a, rfl⟩
#align odd_bit1 odd_bit1

@[simp]
theorem range_two_mul_add_one (α : Type _) [Semiring α] :
    (Set.range fun x : α => 2 * x + 1) = { a | Odd a } := by
  ext x
  simp [Odd, eq_comm]
#align range_two_mul_add_one range_two_mul_add_one

theorem Even.add_odd : Even m → Odd n → Odd (m + n) := by
  rintro ⟨m, rfl⟩ ⟨n, rfl⟩
  exact ⟨m + n, by rw [mul_add, ← two_mul, add_assoc]⟩
#align even.add_odd Even.add_odd

theorem Odd.add_even (hm : Odd m) (hn : Even n) : Odd (m + n) := by
  rw [add_comm]
  exact hn.add_odd hm
#align odd.add_even Odd.add_even

theorem Odd.add_odd : Odd m → Odd n → Even (m + n) := by
  rintro ⟨m, rfl⟩ ⟨n, rfl⟩
  refine' ⟨n + m + 1, _⟩
  rw [two_mul, two_mul]
  ac_rfl
#align odd.add_odd Odd.add_odd

@[simp]
theorem odd_one : Odd (1 : α) :=
  ⟨0, (zero_add _).symm.trans (congr_arg (· + (1 : α)) (mul_zero _).symm)⟩
#align odd_one odd_one

@[simp]
theorem odd_two_mul_add_one (m : α) : Odd (2 * m + 1) :=
  ⟨m, rfl⟩
#align odd_two_mul_add_one odd_two_mul_add_one

theorem Odd.map [RingHomClass F α β] (f : F) : Odd m → Odd (f m) := by
  rintro ⟨m, rfl⟩
  exact ⟨f m, by simp [two_mul]⟩
#align odd.map Odd.map

@[simp]
theorem Odd.mul : Odd m → Odd n → Odd (m * n) := by
  rintro ⟨m, rfl⟩ ⟨n, rfl⟩
  refine' ⟨2 * m * n + n + m, _⟩
  rw [mul_add, add_mul, mul_one, ← add_assoc, one_mul, mul_assoc, ← mul_add, ← mul_add, ← mul_assoc,
    ← Nat.cast_two, ← Nat.cast_comm]
#align odd.mul Odd.mul

theorem Odd.pow (hm : Odd m) : ∀ {a : ℕ}, Odd (m ^ a)
  | 0 => by
    rw [pow_zero]
    exact odd_one
  | a + 1 => by
    rw [pow_succ]
    exact hm.mul <| Odd.pow hm
#align odd.pow Odd.pow

end WithOdd

end Semiring

section Monoid

variable [Monoid α] [HasDistribNeg α] {a : α} {n : ℕ}

theorem Odd.neg_pow : Odd n → ∀ a : α, (-a) ^ n = -a ^ n := by
  rintro ⟨c, rfl⟩ a
  simp_rw [pow_add, pow_mul, neg_sq, pow_one, mul_neg]
#align odd.neg_pow Odd.neg_pow

theorem Odd.neg_one_pow (h : Odd n) : (-1 : α) ^ n = -1 := by rw [h.neg_pow, one_pow]
#align odd.neg_one_pow Odd.neg_one_pow

end Monoid

section CanonicallyOrderedCommSemiring

variable [CanonicallyOrderedCommSemiring α]

-- this holds more generally in a `CanonicallyOrderedAddMonoid` if we refactor `Odd` to use
-- either `2 • t` or `t + t` instead of `2 * t`.
theorem Odd.pos [Nontrivial α] {n : α} (hn : Odd n) : 0 < n := by
  obtain ⟨k, rfl⟩ := hn
  rw [pos_iff_ne_zero, Ne.def, add_eq_zero_iff, not_and']
  exact fun h => (one_ne_zero h).elim
#align odd.pos Odd.pos

end CanonicallyOrderedCommSemiring

section Ring

variable [Ring α] {a b : α} {n : ℕ}

/- Porting note: attribute `simp` removed based on linter report
simp can prove this:
  by simp only [even_neg, even_two]
-/
-- @[simp]
theorem even_neg_two : Even (-2 : α) := by simp only [even_neg, even_two]
#align even_neg_two even_neg_two

theorem Odd.neg (hp : Odd a) : Odd (-a) := by
  obtain ⟨k, hk⟩ := hp
  use -(k + 1)
  rw [mul_neg, mul_add, neg_add, add_assoc, two_mul (1 : α), neg_add, neg_add_cancel_right, ←
    neg_add, hk]
#align odd.neg Odd.neg

@[simp]
theorem odd_neg : Odd (-a) ↔ Odd a :=
  ⟨fun h => neg_neg a ▸ h.neg, Odd.neg⟩
#align odd_neg odd_neg

/- Porting note: attribute `simp` removed based on linter report
simp can prove this:
  by simp only [odd_neg, odd_one]
-/
-- @[simp]
theorem odd_neg_one : Odd (-1 : α) := by simp
#align odd_neg_one odd_neg_one

theorem Odd.sub_even (ha : Odd a) (hb : Even b) : Odd (a - b) := by
  rw [sub_eq_add_neg]
  exact ha.add_even hb.neg
#align odd.sub_even Odd.sub_even

theorem Even.sub_odd (ha : Even a) (hb : Odd b) : Odd (a - b) := by
  rw [sub_eq_add_neg]
  exact ha.add_odd hb.neg
#align even.sub_odd Even.sub_odd

theorem Odd.sub_odd (ha : Odd a) (hb : Odd b) : Even (a - b) := by
  rw [sub_eq_add_neg]
  exact ha.add_odd hb.neg
#align odd.sub_odd Odd.sub_odd

theorem odd_abs [LinearOrder α] : Odd (abs a) ↔ Odd a := by
  cases' abs_choice a with h h <;> simp only [h, odd_neg]
#align odd_abs odd_abs

end Ring

section Powers

set_option linter.deprecated false

variable [LinearOrderedRing R] {a : R} {n : ℕ}

theorem Even.pow_nonneg (hn : Even n) (a : R) : 0 ≤ a ^ n := by
  cases' hn with k hk ; simpa only [hk, two_mul] using pow_bit0_nonneg a k
#align even.pow_nonneg Even.pow_nonneg

theorem Even.pow_pos (hn : Even n) (ha : a ≠ 0) : 0 < a ^ n := by
  cases' hn with k hk ; simpa only [hk, two_mul] using pow_bit0_pos ha k
#align even.pow_pos Even.pow_pos

theorem Odd.pow_nonpos (hn : Odd n) (ha : a ≤ 0) : a ^ n ≤ 0 := by
  cases' hn with k hk ; simpa only [hk, two_mul] using pow_bit1_nonpos_iff.mpr ha
#align odd.pow_nonpos Odd.pow_nonpos

theorem Odd.pow_neg (hn : Odd n) (ha : a < 0) : a ^ n < 0 := by
  cases' hn with k hk ; simpa only [hk, two_mul] using pow_bit1_neg_iff.mpr ha
#align odd.pow_neg Odd.pow_neg

theorem Odd.pow_nonneg_iff (hn : Odd n) : 0 ≤ a ^ n ↔ 0 ≤ a :=
  ⟨fun h => le_of_not_lt fun ha => h.not_lt <| hn.pow_neg ha, fun ha => pow_nonneg ha n⟩
#align odd.pow_nonneg_iff Odd.pow_nonneg_iff

theorem Odd.pow_nonpos_iff (hn : Odd n) : a ^ n ≤ 0 ↔ a ≤ 0 :=
  ⟨fun h => le_of_not_lt fun ha => h.not_lt <| pow_pos ha _, hn.pow_nonpos⟩
#align odd.pow_nonpos_iff Odd.pow_nonpos_iff

theorem Odd.pow_pos_iff (hn : Odd n) : 0 < a ^ n ↔ 0 < a :=
  ⟨fun h => lt_of_not_le fun ha => h.not_le <| hn.pow_nonpos ha, fun ha => pow_pos ha n⟩
#align odd.pow_pos_iff Odd.pow_pos_iff

theorem Odd.pow_neg_iff (hn : Odd n) : a ^ n < 0 ↔ a < 0 :=
  ⟨fun h => lt_of_not_le fun ha => h.not_le <| pow_nonneg ha _, hn.pow_neg⟩
#align odd.pow_neg_iff Odd.pow_neg_iff

theorem Even.pow_pos_iff (hn : Even n) (h₀ : 0 < n) : 0 < a ^ n ↔ a ≠ 0 :=
  ⟨fun h ha => by
    rw [ha, zero_pow h₀] at h
    exact lt_irrefl 0 h, hn.pow_pos⟩
#align even.pow_pos_iff Even.pow_pos_iff

theorem Even.pow_abs {p : ℕ} (hp : Even p) (a : R) : |a| ^ p = a ^ p := by
  rw [← abs_pow, abs_eq_self]
  exact hp.pow_nonneg _
#align even.pow_abs Even.pow_abs

@[simp]
theorem pow_bit0_abs (a : R) (p : ℕ) : |a| ^ bit0 p = a ^ bit0 p :=
  (even_bit0 _).pow_abs _
#align pow_bit0_abs pow_bit0_abs

theorem Odd.strictMono_pow (hn : Odd n) : StrictMono fun a : R => a ^ n := by
  cases' hn with k hk ; simpa only [hk, two_mul] using strictMono_pow_bit1 _
#align odd.strict_mono_pow Odd.strictMono_pow

end Powers

/-- Simp attribute for lemmas about `even` -/
register_simp_attr parity_simps