/-
Written by Congzhou M Sha (cms6712@psu.edu)

Copyright 2022 Congzhou M Sha

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

-/
import data.real.basic
import tactic
import analysis.normed.group.basic
import algebra.order.absolute_value
import algebra.order.group
import algebra.group_power.basic
import data.nat.choose.basic
import data.nat.choose.sum
import algebra.big_operators.basic
import tactic.linarith
import analysis.calculus.fderiv
import analysis.special_functions.pow
import analysis.calculus.cont_diff
import data.num.lemmas

open polynomial nat finset filter asymptotics set metric
open_locale topological_space big_operators
namespace power_rule

  @[simp] def x_to_n : (ℕ → ℝ → ℝ) := (λ (n : ℕ) (x : ℝ), x ^ n)
  @[simp] def x_to_n' : (ℕ → ℝ → ℝ)
  | 0 := λ (x : ℝ), (0 : ℝ)
  | (n + 1) := λ (x : ℝ), (n + 1 : ℝ) * x ^ n

  theorem geometric_series_no_division (x : ℝ) (n : ℕ) :
    finset.sum (range n) (λ k, (x-1) * x ^ (k : ℝ)) = x ^ n - 1 :=
  begin
    apply sum_range_induction,
    simp,
    intro m,
    simp,
    ring_nf,
  end

  lemma mul_sum_left_distrib (f : ℝ → ℝ → ℝ) (g : ℕ → ℝ) (x : ℝ) (y : ℝ) (n : ℕ) :
    (f x y) * (range n).sum g = (range n).sum (λ k, (f x y) * (g k)) :=
  begin
    induction n with n h,
    { simp, },
    {
      rw finset.sum_range_succ,
      rw left_distrib,
      dsimp,
      rw h,
      rw finset.sum_range_succ,
    }
  end

  lemma unfold_last_term : ∀ (x y : ℝ) (n : ℕ) (f : ℕ → ℝ),
    (n > 0) → (range n).sum f = (range (n - 1)).sum (λ k, f k) + f (n-1) :=
  begin
    intros x y n f p,
    induction n with n ih,
    { simp, linarith,},
    {
      rw range_succ,
      simp,
      ring,
    },
  end

  lemma unfold_first_term : ∀ (x y : ℝ) (n : ℕ) (f : ℕ → ℝ),
    (n > 0) → (range n).sum f = f 0 + (range (n-1)).sum (λ k, f (k + 1)) :=
  begin
    intros x y n f p,
    induction n with n ih,
    { simp, linarith, },
    {
      induction n with n₁ ih₁,
      { norm_num, },
      {
        rw range_succ,
        simp,
        have q : n₁.succ > 0, by simp,
        have r := ih q,
        rw r,
        clear ih ih₁ p,
        have s := (unfold_last_term x y n₁.succ f) q,
        rename_var x k,
        simp,
        rw range_succ,
        simp,
        rw nat.succ_eq_add_one,
        linarith,
      },
    }
  end

  theorem x_to_n_sub_y_to_n_factor (x y : ℝ) (n : ℕ) : x^n-y^n = (x-y) * (range n).sum (λ k, x ^ (n - 1 - k) * y ^ k) := begin
    rw finset.mul_sum,
    induction n with n ih,
    { simp, },
    {
      rw [pow_succ, pow_succ, finset.sum_range_succ],
      simp,
      rw [mul_sub_right_distrib, ← add_sub_assoc, sub_left_inj],
      rw sub_eq_iff_eq_add at ih,
      rw [ih, left_distrib, finset.mul_sum, add_left_inj, finset.sum_congr rfl],
      intros k p,
      rw [← mul_assoc, ← mul_assoc, mul_comm, mul_comm x (x-y),
        mul_assoc (x-y) x (x ^ (n - 1 - k)), ← pow_succ],

      have q : n - 1 - k + 1 = n - k, begin
        rw nat.sub.right_comm,
        rw finset.mem_range at p,
        have q₁ : 0 < n - k, begin
          exact nat.sub_pos_of_lt p,
        end,
        have q₂ : 1 ≤ n - k, by linarith,
        rw nat.sub_add_cancel q₂,
      end,

      rw q,
      ring_nf,
    }
  end

  lemma pos_mul_ge_one_ge_one (a b : ℝ) (h₁ : a ≥ 1) (h₂ : b > 0) : (a * b ≥ b) := begin
    nlinarith,
  end

  lemma weaken_le (a b c : ℝ) (h : a ≤ b ∧ 0 < c) : (a ≤ b + c) :=
  begin
    have h₁ := h.1,
    have h₂ := h.2,
    nlinarith,
  end

  lemma deriv_power_ineq_helper_1 (x y : ℝ) (n k : ℕ)
  (h : -1 < y - x ∧ y - x < 1)
  (p : k ≤ n) :
  |x| ^ k ≤ (|x| + 1) ^ k :=
  begin
    have v₁ : |x| ≤ |x| + 1,
      begin
        have w₁ := le_abs_self x,
        linarith,
      end,
      induction k with k ih,
      {
        simp,
      },
      {
        have : k ≤ n, begin
          rw succ_eq_add_one at p,
          linarith,
        end,
        rw [pow_succ, pow_succ],
        have w₁ := le_abs_self x,
        have w₂ := ih this,
        have w₃ : |x| + 1 ≥ 1 :=
        begin
          have := abs_nonneg x,
          linarith,
        end,
        rw [right_distrib, one_mul],
        have w₄ : 0 < (|x| + 1), by nlinarith,
        have w₅ := pow_pos w₄,
        apply weaken_le,
        split,
        {
          nlinarith,
        },
        {
          specialize w₅ k,
          exact w₅,
        },
      },
  end

  lemma deriv_power_ineq_helper_2
    (x y : ℝ) (n k : ℕ)
    (h : -1 < y - x ∧ y - x < 1)
    (p : k ≤ n) : |y| ^ (n - k) ≤ (|x| + 1) ^ (n - k) :=
  begin
    have h₁ := h.1,
    have h₂ := h.2,
    clear h,
    have h₃ : |y| < |x| + 1,
    begin
      have q := le_abs_self x,
      rw abs_lt,
      split,
      {
        rw [neg_eq_zero_sub, sub_add_eq_sub_sub, sub_lt_iff_lt_add,
          ← neg_eq_zero_sub, neg_lt, lt_abs],
        right,
        linarith,
      },
      {
        linarith,
      },
    end,
    induction (n-k) with n ih,
    { simp, },
    {
      rw [pow_succ, pow_succ],
      have w₁ := le_abs_self x,
      have w₂ := ih,
      have w₃ : |x| + 1 ≥ 1 :=
      begin
        have := abs_nonneg x,
        linarith,
      end,
      have w₄ : |y| ≤ |x| + 1, by linarith,
      have w₅ := abs_nonneg y,
      have w₆ : 0 < |x| + 1, by linarith,
      have w₇ := pow_pos w₆,
      have w₈ := pow_nonneg w₅,
      specialize w₈ n,
      replace w₆ : 0 ≤ |x| + 1, by linarith,
      exact mul_le_mul w₄ ih w₈ w₆,
    },
  end

  lemma deriv_power_ineq'
    (x y : ℝ) (n k : ℕ)
    (h : -1 < y - x ∧ y - x < 1)
    (p : k ≤ n) :
    |y ^ (n - k) * x ^ k| ≤ (|x| + 1) ^ n :=
  begin
    have u₁ := deriv_power_ineq_helper_1 x y n k h p,
    have u₂ := deriv_power_ineq_helper_2 x y n k h p,
    rw abs_mul,
    repeat {rw abs_pow},
    have u₃ := pow_nonneg (abs_nonneg y),
    have u₄ : 0 ≤ (|x| + 1), begin
      have := abs_nonneg x,
      linarith,
    end,
    specialize u₃ (n-k),
    replace u₄ := pow_nonneg u₄,
    specialize u₄ k,
    have q := mul_le_mul u₁ u₂ u₃ u₄,
    rw ← pow_add at q,
    rw ← nat.add_sub_assoc at q,
    rw (nat.add_sub_cancel_left k n) at q,
    rw mul_comm,
    exact q,
    exact p,
  end

  lemma sum_cons_simp (x y : ℝ) (n : ℕ) : ∑ (x_1 : ℕ) in range (n + 1), (|x| + 1) ^ n = (n + 1) * (|x| + 1) ^ n :=
  begin
    simp,
  end

  lemma sum_one_plus_abs_pos (x : ℝ) (n : ℕ) :  ∀ i ∈ range (n+1), 0 < (|x| + 1) ^ n :=
  begin
    intros i p,
    have := abs_nonneg x,
    have h₁ : 0 < |x| + 1, by linarith,
    have h₂ := pow_pos h₁,
    specialize h₂ n,
    exact h₂,
  end

    lemma sum_one_plus_abs_nonneg (x : ℝ) (n : ℕ) : ∀ i ∈ range (n + 1), 0 ≤ (|x| + 1) ^ n :=
  begin
    intros i p,
    have := abs_nonneg x,
    have h₁ : 0 ≤ |x| + 1, by linarith,
    have h₂ := pow_nonneg h₁,
    specialize h₂ n,
    exact h₂,
  end
  lemma sum_power_bound_constant (x : ℝ) (n : ℕ) : ∑ (x_1 : ℕ) in range (n + 1), (|x| + 1) ^ n = (n+1) * (|x| + 1) ^ n :=
  begin
    simp,
  end

  lemma one_plus_abs_pos (x : ℝ) : 0 < 1 + |x| := begin
    have := abs_nonneg x,
    linarith,
  end

  lemma one_plus_abs_nonneg (x : ℝ) : 0 ≤ 1 + |x| := begin
    have := abs_nonneg x,
    linarith,
  end

  lemma one_plus_abs_pow_pos (x : ℝ) : ∀ (n:ℕ), 0 < (1 + |x|) ^ n := pow_pos (one_plus_abs_pos x)

  lemma one_plus_abs_pow_nonneg (x : ℝ) (n : ℕ) : 0 ≤ (1 + |x|) ^ n :=
  begin
    have := one_plus_abs_pow_pos x n,
    linarith,
  end

  lemma bound_xy_y (x y : ℝ) (r : |y - x| < 1) : |y| < 1 + |x| := begin
    have := abs_abs_sub_abs_le_abs_sub y x,
    have : | |y| - |x| | < 1, by linarith,
    rw abs_lt at this,
    linarith,
  end

  lemma bound_x (x : ℝ) : |x| ≤ 1 + |x| := begin linarith, end

  lemma bound_x_pow_one (x : ℝ) (n : ℕ) : 1 ≤ (1 + |x|) ^ n :=
  begin
    induction n with n ih,
    { simp, },
    {
      rw pow_succ,
      have a := abs_nonneg x,
      have b : 1 ≤ 1 + |x|, by linarith,
      have c : (0 : ℝ) ≤ 1, by norm_num,
      have := mul_le_mul b ih c (one_plus_abs_nonneg x),
      rw one_mul at this,
      exact this,
    },
  end

  lemma bound_x_pow (x : ℝ) (n : ℕ) : |x| ^ n ≤ (1 + |x|) ^ n :=
  begin
    induction n with n ih,
    {
      have := bound_x x,
      linarith,
    },
    {
      rw [pow_succ, pow_succ],
      have a := one_plus_abs_pos x,
      have b := one_plus_abs_pow_pos x n,
      have := abs_nonneg x,
      have f := pow_nonneg this,
      specialize f n,
      have e : 0 ≤ (1 + |x|) ^ n, by linarith,
      have c : |x| ≤ |x|, by linarith,
      have d : |x| ≤ 1 + |x|, by linarith,
      have : 0 ≤ 1 + |x|, by linarith,
      exact mul_le_mul d ih f this,
    },
  end

  lemma pow_abs_nonneg (x : ℝ) (n : ℕ) : 0 ≤ (|x|) ^ n :=
  begin
    have := abs_nonneg x,
    have := pow_nonneg this,
    specialize this n,
    exact this,
  end

  lemma power_abs_y_bound (x y : ℝ) (q : |y| ≤ 1 + |x|) (k : ℕ) : |y| ^ k ≤ (1 + |x|) ^ k :=
  begin
    induction k with k ih,
    { simp, },
    {
      rw [pow_succ, pow_succ],
      have a := pow_abs_nonneg y k,
      have := abs_nonneg x,
      have b : 0 ≤ 1 + |x|, by linarith,
      exact mul_le_mul q ih a b,
    },
  end

  lemma power_abs_x_bound (x y : ℝ) (k : ℕ)  : |x| ^ k ≤ (1 + |x|) ^ k :=
  begin
    induction k with k ih,
    { simp, },
    {
      rw [pow_succ, pow_succ],
      have a := pow_abs_nonneg x k,
      have := abs_nonneg x,
      have f : 0 ≤ 1 + |x|, by linarith,
      have b := bound_x x,
      exact mul_le_mul b ih a f,
    },
  end

  lemma function_bound (x y : ℝ) (q : |y| ≤ 1 + |x|) (n : ℕ):
    ∀ (x_1 : ℕ), x_1 ∈ range (n + 1) → |y ^ (n - x_1) * x ^ x_1| ≤ |(1+|x|) ^ n| :=
  begin
    intros k p,
    rw abs_mul,
    repeat {rw abs_pow},
    have a : ∀ (k : ℕ), |y| ^ k ≤ (1 + |x|) ^ k :=
    begin
      intro k,
      clear p,
      induction k with k ih,
      { simp, },
      {
        rw [pow_succ, pow_succ],
        have a := pow_abs_nonneg y k,
        have := abs_nonneg x,
        have b : 0 ≤ 1 + |x|, by linarith,
        exact mul_le_mul q ih a b,
      },
    end,
    specialize a (n-k),

    have b := bound_x x,
    have c : ∀ (k : ℕ), |x| ^ k ≤ (1 + |x|) ^ k :=
    begin
      intro k,
      clear p,
      induction k with k ih,
      { simp, },
      {
        rw [pow_succ, pow_succ],
        have a := pow_abs_nonneg x k,
        have := abs_nonneg x,
        have f : 0 ≤ 1 + |x|, by linarith,
        exact mul_le_mul b ih a f,
      },
    end,
    specialize c k,

    have := abs_nonneg x,
    have q : 0 ≤ 1 + |x|, by linarith,
    have := pow_nonneg q,
    specialize this (n - k),

    have d := mul_le_mul a c (pow_abs_nonneg x k) this,
    rw finset.mem_range_succ_iff at p,
    rw pow_sub_mul_pow (1 + |x|) p at d,
    rw ← abs_of_nonneg q at d,
    exact d,
  end

  lemma sum_ineq (x y c : ℝ) (h : |y| ≤ 1 + |x|) (n : ℕ) :
    |∑ (x_1 : ℕ) in range (n + 1), y ^ (n - x_1) * x ^ x_1|
    ≤ |∑ (x_1 : ℕ) in range (n + 1), (1 + |x|) ^ n| :=
  begin
    have q₁ := bound_x x,
    have q₂ := finset.abs_sum_le_sum_abs (λ x_1, y ^ (n - x_1) * x ^ x_1) (range (n + 1)),
    simp at q₂,
    have q₃ := function_bound x y h n,
    have q₄ := finset.sum_le_sum q₃,
    simp at q₄,
    simp,
    have := one_plus_abs_pow_pos x n,
    have a : 0 ≤ (1 + |x|) ^ n, by linarith,
    have : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have : (0 : ℝ) ≤ ↑n + 1, by linarith,
    have : 0 ≤ (↑n + 1) * (1 + |x|) ^ n := mul_nonneg this a,
    rw abs_of_nonneg this,
    clear this this this,
    rw ← abs_of_pos (one_plus_abs_pos x),
    linarith,
  end

  lemma power_limit_denom_pos (x : ℝ) (n : ℕ) :
    0 < ((↑n + 2) * (↑n + 1) * (1 + |x|) ^ n) :=
  begin
    have a : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have b : (0 : ℝ) < 2, by norm_num,
    have c := add_pos_of_nonneg_of_pos a b,
    have b : (0 : ℝ) < 1, by norm_num,
    have d := add_pos_of_nonneg_of_pos a b,
    have e := one_plus_abs_pow_pos x n,
    have := mul_pos c (mul_pos d e),
    rw ← mul_assoc at this,
    exact this,
  end

  lemma power_limit_div_pos (x : ℝ) (n : ℕ) (c : ℝ) (r : 0 < c) :
    0 < c / ((↑n + 2) * (↑n + 1) * (1 + |x|) ^ n) :=
  begin
    have a := power_limit_denom_pos x n,
    exact div_pos r a,
  end

  lemma ineq_1 (x : ℝ) (n : ℕ) : |(x ^ n * ↑n + x ^ n)| ≤ |2 * (↑n+2) * (1+|x|)^(n+1)| := begin
    have := one_plus_abs_pow_pos x (n + 1),
    have := le_abs_self x,

    have a : 1*|x ^ n| ≤ (↑n + 2) * (1 + |x|) ^ (n + 1) := begin
      have a := bound_x_pow x n,
      have f : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
      have : (0 : ℝ) < (1 : ℝ), by norm_num,
      have b: (0 : ℝ) ≤ ↑n + 1, by linarith,
      have g : (1 : ℝ) ≤ ↑n + 2, by linarith,
      have h : (0 : ℝ) ≤ ↑n + 2, by linarith,
      have := abs_nonneg x,
      have i := pow_nonneg this,
      specialize i n,
      have c : 1 ≤ 1 + |x|, by linarith,
      have d : 0 ≤ 1 + |x|, by linarith,
      have e := mul_le_mul c a (pow_abs_nonneg x n) d,
      simp at e,
      rw ← pow_succ at e,
      rw ← pow_abs,
      exact mul_le_mul g e i h,
    end,
    have b : (|x| ^ n * ↑n) ≤ (n + 2) * (1 + |x|) ^ (n+1) := begin
      rw mul_comm,
      have a := bound_x_pow x n,
      have b : ↑n ≤ ↑n + (1 : ℝ), by linarith,
      refine mul_le_mul _ _ _ _,
      linarith,
      have := abs_nonneg x,
      have c : 1 ≤ 1 + |x|, by linarith,
      have d : 0 ≤ 1 + |x|, by linarith,
      have e := mul_le_mul c a (pow_abs_nonneg x n) d,
      rw one_mul at e,
      rw pow_succ,
      exact e,
      exact pow_abs_nonneg x n,
      have f := nat.cast_nonneg n,
      have g : (0 : ℝ) ≤ 1, by norm_num,
      have : (0 : ℝ) ≤ 2, by linarith,
      have := add_le_add f this,
      linarith,
    end,

    rw one_mul at a,
    have c : (↑n + (1:ℝ) ≤ ↑n + 2), by linarith,
    have d := one_plus_abs_pow_pos x (n + 1),
    replace d : 0 ≤ (1 + |x|) ^ (n + 1), by linarith,
    have e := abs_add (x ^ n * ↑n) (x ^ n),
    have g := one_plus_abs_pow_pos x (n + 1),
    have g' : 0 ≤ (1 + |x|) ^ (n+1), by linarith,
    have f : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have h : (0 : ℝ) ≤ ↑n + 2, by linarith,
    have j : (0 : ℝ) ≤ 2, by norm_num,
    have i := mul_nonneg h g',
    replace j := mul_nonneg j i,
    rw ← mul_assoc at j,
    rw abs_of_nonneg j,
    have : (2 : ℝ) = 1 + 1, by norm_num,
    rw [this, right_distrib, one_mul, ← this, right_distrib],
    rw ← abs_pow at b,
    rw ← abs_of_nonneg f at b,
    rw ← abs_mul at b,
    rw abs_of_nonneg f at *,
    linarith,
  end

  lemma expr_nonneg (x y : ℝ) (n : ℕ) (h : 0 < |y-x|): 0 ≤ |y - x| * ((↑n + 3) * (↑n + 2) * (1 + |x|) ^ (n + 1)) :=
  begin
    have p₂ := one_plus_abs_pow_pos x (n + 1),
    have p₃ : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have p₄ : (0 : ℝ) < ↑n + 2, by linarith,
    have p₅ : (0 : ℝ) < ↑n + 3, by linarith,
    have p₆ := mul_pos p₅ p₄,
    have p₇ := mul_pos p₆ p₂,
    have p₈ := mul_pos h p₇,
    linarith,
  end

  lemma one_plus_abs_mono_inc (x : ℝ) (n : ℕ) : (1 + |x|) ^ n ≤ (1 + |x|) ^ (n + 1) :=
  begin
    induction n with n ih,
    { simp, },
    {
      rw pow_succ,
      rw pow_succ,
      have a : 1 + |x| ≤ 1 + |x|, by linarith,
      refine mul_le_mul _ _ _ _,
      exact a,
      exact ih,
      have := one_plus_abs_pow_pos x n,
      linarith,
      have := one_plus_abs_pos x,
      linarith,
    },
  end

  lemma one_plus_abs_mono_inc_pair (x : ℝ) (m n : ℕ) : (1 + |x|) ^ m ≤ (1 + |x|) ^ (m + n) :=
  begin
    induction m with m ih,
    { simp, exact bound_x_pow_one x n, },
    {
      rw [pow_succ, nat.succ_add, pow_succ],
      have : 1 + |x| ≤ 1 + |x|, by linarith,
      exact mul_le_mul this ih (one_plus_abs_pow_nonneg x m) (one_plus_abs_nonneg x),
    }
  end

  lemma one_plus_abs_mono_inc_le (x : ℝ) (m n : ℕ) (h : m ≤ n) : (1 + |x|) ^ m ≤ (1 + |x|) ^ n :=
  begin
    have a := one_plus_abs_mono_inc_pair x m (n - m),
    rw nat.add_sub_of_le h at a,
    assumption,
  end

  lemma expr_lt (x : ℝ) (n : ℕ) : (↑n + 1) * (1 + |x|) ^ n ≤ (↑n + 2) * (1 + |x|) ^ (n + 1) :=
  begin
    have a := one_plus_abs_mono_inc x n,
    have a' := one_plus_abs_pow_nonneg x n,
    have b : 1 ≤ 2, by norm_num,
    have c : (↑n + (1:ℝ)) ≤ (↑n + (2 : ℝ)), by linarith,
    have c' : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,

    refine mul_le_mul _ _ _ _,
    repeat {linarith},
  end

  lemma ineq_1_pos (x : ℝ) (n : ℕ) : 0 < (↑n + 1) * (1 + |x|) ^ n :=
  begin
    have a' := one_plus_abs_pow_pos x n,
    have c' : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have c : (0:ℝ) < ↑n + 1, by linarith,
    exact mul_pos c a',
  end

  lemma ineq_2_pos (x : ℝ) (n : ℕ) : 0 < 2 * (↑n + 2) * (1 + |x|) ^ (n + 1) :=
  begin
    have a' := one_plus_abs_pow_pos x (n+1),
    have c' : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have c : (0:ℝ) < ↑n + 2, by linarith,
    have d := mul_pos c a',
    have : (0:ℝ) < 2, by norm_num,
    have := mul_pos this d,
    linarith,
  end

  lemma ineq_3 (x : ℝ) (n : ℕ) : 3 * (↑n + 2) * (1 + |x|) ^ (n + 1) ≤ (↑n + 3) * (↑n + 2) * (1 + |x|) ^ (n + 1) :=
  begin
    have a : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have a' : (0 : ℝ) ≤ ↑n + 2, by linarith,
    have a'' : (0 : ℝ) ≤ ↑n + 3, by linarith,
    have a₁ : (3 : ℝ) ≤ ↑n + 3, by linarith,
    have a₂ : (↑n + (2 : ℝ)) ≤ ↑n + 2, by linarith,
    have b := one_plus_abs_pow_nonneg x (n + 1),
    refine mul_le_mul _ _ _ _,
    repeat {linarith},
    refine mul_le_mul _ _ _ _,
    repeat {linarith},
    refine mul_nonneg _ _,
    repeat {linarith},
  end

  lemma individual_to_sum (x : ℝ) (n : ℕ) : x ^ n * ↑n + x ^ n = ∑ (x_1 : ℕ) in range (n+1), x ^ n :=
  begin
    simp,
    rw right_distrib,
    rw one_mul,
    rw mul_comm,
  end
  lemma sub_sum_eq_sum_sub (x : ℝ) (n : ℕ) (f : ℕ → ℝ) (g : ℕ → ℝ) :
  ∑ (x_1 : ℕ) in range n, (f x_1 - g x_1) = (∑ (x_1 : ℕ) in range n, f x_1) - (∑ (x_1 : ℕ) in range n, g x_1) :=
  begin
    simp,
  end

  lemma factor_out (x y : ℝ) (n : ℕ) :
    ∑ (x_1 : ℕ) in range (n + 1), (y ^ (n - x_1) * x ^ x_1 - x ^ n) =
    ∑ (x_1 : ℕ) in range (n + 1), x ^ x_1 * (y ^ (n - x_1) - x ^ (n - x_1)) :=
  begin
    apply finset.sum_congr rfl,
    intros k p,
    rw finset.mem_range at p,
    have : k ≤ n, by linarith,
    rw mul_sub_left_distrib,
    rw mul_comm,
    rw pow_mul_pow_sub x this,
  end

  lemma rw_inner_expr (x y : ℝ) (n : ℕ) :
    ∑ (x_1 : ℕ) in range (n + 1), x ^ x_1 * (y ^ (n - x_1) - x ^ (n - x_1)) =
    ∑ (x_1 : ℕ) in range (n + 1), x ^ x_1 * (y - x) * ∑ (x_2 : ℕ) in range (n - x_1), y ^ (n - x_1 - 1 - x_2) * x ^ x_2 :=
  begin
    apply finset.sum_congr rfl,
    intros k p,
    rw mul_assoc,
    rw x_to_n_sub_y_to_n_factor y x (n - k),
  end

  lemma ineq_inner_sum (x y : ℝ) (n : ℕ) (h : |y| ≤ 1 + |x|):
    |∑ (k : ℕ) in range (n + 1), y ^ (n - k) * x ^ k| ≤ (n + 1) * (1 + |x|) ^ n :=
  begin
    have a := sum_ineq x y n h n,
    simp at a,
    have b := one_plus_abs_pow_nonneg x n,
    have : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have c : (0 : ℝ) ≤ ↑n + 1, by linarith,
    have d := mul_nonneg c b,
    have := abs_of_nonneg d,
    rw this at a,
    assumption,
  end

  lemma function_bound_2 (x y : ℝ) (n : ℕ) : ∀ (i : ℕ), i ∈ range (n + 1) →
    |x ^ i * (y - x) * ∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2|
    ≤ |x ^ i * (y - x)| * |∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2| :=
  begin
    intros i p,
    rw abs_mul,
  end

  lemma coe_nat_ineq (n i : ℕ) (h : i < n) : ↑(n - i - 1) + (1 : ℝ) = ↑(n-i) :=
  begin
    have : n ≥ i + 1, by linarith,
    have : n - i ≥ 1 := le_tsub_of_add_le_left this,
    have : 1 ≤ n - i, by linarith,
    rw nat.cast_sub this,
    simp,
  end

  lemma weird_nat_ineq_1 (n i : ℕ) : n - i - 1 ≤ n - i := nat.sub_le (n - i) 1
  /-
  lemma function_bound_2_5 (x : ℝ) (n i : ℕ) :
    ∑ (x_2 : ℕ) in range (n - i), |(1 + |x|) ^ (n - i - 1 - x_2) * (1 + |x|) ^ x_2|
    = ∑ (x_2 : ℕ) in range (n - i), (1 + |x|) ^ (n - i - 1) :=
  begin
    rw finset.sum_congr rfl,
    intros k p,
    rw finset.mem_range at p,

    have a := weird_nat_ineq_1 n i,

    rw ← abs_of_pos (one_plus_abs_pow_pos x (n - i - 1)),
    rw ← pow_add,
    rw nat.sub_add_cancel this,
  end
  -/

  lemma function_bound_3_pos (x y : ℝ) (n i : ℕ) : 0 ≤ ↑(n - i) * (1 + |x|) ^ (n - i - 1) :=
  begin
    have a : (0 : ℝ) ≤ ↑(n - i) := nat.cast_nonneg (n - i),
    have b := one_plus_abs_pow_nonneg x (n - i - 1),
    exact mul_nonneg a b,
  end

  lemma function_bound_3 (x y : ℝ) (n i : ℕ) (h : |y| ≤ 1 + |x|) : ∀ (k : ℕ), k ∈ range(n - i) →
    |y ^ (n - i - 1 - k) * x ^ k|
    ≤ (1 + |x|) ^ (n - i - 1) :=
  begin
    intros k p,
    rw finset.mem_range at p,
    have p := le_pred_of_lt p,
    rw ← abs_of_pos (one_plus_abs_pow_pos x (n - i - 1)),
    rw abs_mul,
    repeat {rw abs_pow},
    have a := power_abs_x_bound x y k,
    have b := power_abs_y_bound x y h (n - i - 1 - k),

    have := abs_nonneg x,
    have q : 0 ≤ 1 + |x|, by linarith,
    have q₁ := pow_nonneg q (n - i - 1 - k),
    have q₂ := pow_nonneg this k,

    have d := mul_le_mul b a q₂ q₁,
    rw pow_sub_mul_pow (1 + |x|) p at d,
    rw ← abs_of_nonneg q at d,
    exact d,
  end

  @[simp] lemma rw_abs_sum (x : ℝ) (n : ℕ) :
    ∑ (i : ℕ) in range (n + 1), |x| ^ i * |↑(n - i) * (1 + |x|) ^ (n - i - 1)|
    = ∑ (i : ℕ) in range (n + 1), |x| ^ i * (↑(n - i) * (1 + |x|) ^ (n - i - 1)) :=
  begin
    rw finset.sum_congr rfl,
    intros i p,
    have a := one_plus_abs_pow_nonneg x (n - i - 1),
    have b : (0 : ℝ) ≤ ↑(n - i) := nat.cast_nonneg (n - i),
    have c := mul_nonneg b a,
    rw ← abs_of_nonneg c,
    rw abs_abs,
  end

  lemma rw_abs_sum_2 (x y : ℝ) (n : ℕ ) :
    ∑ (i : ℕ) in range (n + 1), |x| ^ i * (↑(n - i) * (1 + |x|) ^ (n - i - 1))
    = ∑ (x_1 : ℕ) in range (n + 1), |x| ^ x_1 * ((↑n - ↑x_1) * (1 + |x|) ^ (n - x_1 - 1)) :=
  begin
    rw finset.sum_congr rfl,
    intros i p,
    rw finset.mem_range at p,
    have : i ≤ n, by linarith,
    rw nat.cast_sub this,
  end

  lemma ineq_5 (x y : ℝ) (n : ℕ) (h : |y| ≤ 1 + |x|): ∑ (i : ℕ) in range (n + 1), |x ^ i| * |∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2| ≤
  ∑ (i : ℕ) in range (n + 1), |x ^ i| * |∑ (x_2 : ℕ) in range (n - i), (1 + |x|) ^ (n - i - 1)| :=
  begin
    refine finset.sum_le_sum _,
    intros i p,
    rw finset.mem_range at p,
    refine mul_le_mul _ _ _ _,
    { linarith, },
    {
      simp,
      have a := finset.abs_sum_le_sum_abs (λ x_2, y ^ (n - i - 1 - x_2) * x ^ x_2) (range (n - i)),
      rw abs_of_nonneg (function_bound_3_pos x y n i),

      have b := function_bound_3 x y n i h,
      have c := finset.sum_le_sum b,
      simp at c,
      rename_var x_1 i_1,
      linarith,
    },
    {
      exact abs_nonneg (∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2),
    },
    {
      exact abs_nonneg (x ^ i),
    },
  end

  lemma rw_inner_sum (x y : ℝ) (n : ℕ) : ∑ (i : ℕ) in range (n + 1),
  |x ^ i * ∑ (x_2 : ℕ) in range (n - i),
    y ^ (n - i - 1 - x_2) * x ^ x_2|
      = ∑ (x_1 : ℕ) in range (n + 1),
        |x ^ x_1| * |∑ (x_2 : ℕ) in range (n - x_1),
          y ^ (n - x_1 - 1 - x_2) * x ^ x_2| :=
  begin
    rw finset.sum_congr rfl,
    intros k p,
    rw finset.mem_range at p,
    rw abs_mul,
  end

  lemma ineq_6 (x y : ℝ) (n : ℕ) (h : |y| ≤ 1 + |x|) :
    |y - x| * ∑ (i : ℕ) in range (n + 1), |x ^ i| * |∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2|
      ≤ |y - x| * ∑ (i : ℕ) in range (n + 1), |x ^ i| * ((n - i) * (1 + |x|) ^ (n - i - 1)) :=
  begin
    have d := eq_zero_or_ne_zero (|y-x|),
    cases d,
    { rw d, simp, },
    {
      have : 0 < |y-x| :=
      begin
        have := abs_nonneg (y-x),
        by_contra,
        apply d.out,
        linarith,
      end,

      have h := ineq_5 x y n h,
      simp at h,
      simp,
      refine mul_le_mul _ _ _ _,
      { linarith, },
      {
        rw ← rw_abs_sum_2 x y n,
        linarith,
      },
      {
        have a := rw_inner_sum x y n,
        simp at a,
        rw ← a,
        have := finset.abs_sum_le_sum_abs (λ i, x ^ i * ∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2) (range (n + 1)),
        simp at this,
        have := abs_nonneg (∑ (i : ℕ) in range (n + 1), x ^ i * ∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2),
        linarith,
      },
      {
        exact abs_nonneg (y - x),
      },
    },
  end

  lemma sum_abs_pos (f : ℕ → ℝ) (n : ℕ) :
    0 ≤ ∑ (i : ℕ) in range n, |f i| :=
  begin
    induction n with n ih,
    simp,
    rw finset.sum_range_succ,
    have := abs_nonneg (f n),
    linarith,
  end

  lemma abs_pow_nonneg (x : ℝ) (n : ℕ) : 0 ≤ |x| ^ n :=
  begin
    exact pow_nonneg (abs_nonneg x) n,
  end

  lemma function_bound_4_1 (x y : ℝ) (n : ℕ) (h : |y| ≤ 1 + |x|) : ∀ (i : ℕ), i ∈ range (n + 1) →
  |x ^ i * (y - x) * ∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2| ≤ |(1 + |x|) ^ i| * |y - x| * ((n - i) * (1 + |x|) ^ (n - i - 1)) :=
  begin
    intros i p,
    repeat {rw abs_mul},
    repeat {rw abs_pow},
    have := one_plus_abs_nonneg x,
    have a := power_abs_x_bound x y i,
    have b : |y-x| ≤ |y-x|, by linarith,
    have c := finset.abs_sum_le_sum_abs (λ x_2, y ^ (n - i - 1 - x_2) * x ^ x_2) (range (n - i)),
    rw finset.mem_range at p,
    have : i ≤ n, by linarith,
    rw ← nat.cast_sub this,
    have c' := abs_of_nonneg (function_bound_3_pos x y n i),
    have d := function_bound_3 x y n i h,
    have e := finset.sum_le_sum d,
    simp at e,
    have := mul_le_mul b e (sum_abs_pos (λ i_1, y ^ (n - i - 1 - i_1) * x ^ i_1) (n-i)) (abs_nonneg (y-x)),
    have := mul_le_mul a this (mul_nonneg (abs_nonneg (y - x)) (sum_abs_pos (λ i_1, y ^ (n - i - 1 - i_1) * x ^ i_1) (n-i))) (one_plus_abs_pow_nonneg x i),
    repeat {rw ← mul_assoc},
    repeat {rw ← mul_assoc at this},
    rw abs_of_nonneg (one_plus_abs_nonneg x),
    have f : |x| ^ i ≤ |x| ^ i, by linarith,
    have := mul_le_mul b c (abs_nonneg ∑ (i_1 : ℕ) in range (n - i), y ^ (n - i - 1 - i_1) * x ^ i_1) (abs_nonneg (y-x)),
    have := mul_le_mul f this (mul_nonneg (abs_nonneg (y - x)) (abs_nonneg ∑ (i_1 : ℕ) in range (n - i), y ^ (n - i - 1 - i_1) * x ^ i_1)) (abs_pow_nonneg x i),
    simp at *,
    repeat {rw ← mul_assoc at this},
    linarith,
  end

  lemma function_bound_4 (x y : ℝ) (n : ℕ) (h : |y| ≤ 1 + |x|) :
  ∑ (i : ℕ) in range (n + 1), |x ^ i * (y - x) * ∑ (x_2 : ℕ) in range (n - i), y ^ (n - i - 1 - x_2) * x ^ x_2| ≤
  ∑ (i : ℕ) in range (n + 1), |(1 + |x|) ^ i| * |y - x| * ((n - i) * (1 + |x|) ^ (n - i - 1)) :=
  begin
    have a := function_bound_4_1 x y n h,
    have b := finset.sum_le_sum a,
    assumption,
  end

  lemma ineq_constants (x y : ℝ) (n : ℕ) : ∀ (i : ℕ), i ∈ range (n + 1) →
    (λ i, |(1 + |x|) ^ i| * |y - x| * ((↑n - ↑i) * (1 + |x|) ^ (n - i - 1))) i ≤ (λ i, |y - x| * (↑n + 1) * (1 + |x|) ^ n) i :=
  begin
    intros i p,
    dsimp,
    rw finset.mem_range at p,
    have q : i ≤ n, by linarith,
    rw abs_of_nonneg (one_plus_abs_pow_nonneg x i),
    rw [mul_comm ((1 + |x|) ^ i), ← mul_assoc],
    have := nat.sub.right_comm n i 1,
    have := weird_nat_ineq_1 n i,
    rw [mul_comm, ← mul_assoc, ← mul_assoc,
      mul_comm ((1 + |x|) ^ (n - i - 1)),
      mul_assoc (|y-x|), ← pow_add (1 + |x|) (n - i - 1) i,
      mul_assoc, mul_comm ((1 + |x|) ^ (n - i - 1 + i)), ← mul_assoc],
    have : n - i - 1 + i ≤ n, by linarith,
    have a := one_plus_abs_mono_inc_le x (n - i - 1 + i) n this,
    have : (0 : ℝ) ≤ i := nat.cast_nonneg i,
    have a' : (↑n - ↑i : ℝ) ≤ ↑n + 1, by linarith,
    have : (0 : ℝ) ≤ n := nat.cast_nonneg n,
    have : (0 : ℝ) ≤ n + 1, by linarith,
    have b := mul_le_mul a' a (one_plus_abs_pow_nonneg x (n - i - 1 + i)) this,
    have c := abs_nonneg (y-x),
    have d: |y-x| ≤ |y-x|, by linarith,
    rw [mul_assoc, mul_assoc],
    refine mul_le_mul d b _ _,
    {
      have a: (0 : ℝ) ≤ ↑(n - i) := nat.cast_nonneg (n - i),
      have := nat.cast_sub q,
      rw ← this,
      exact mul_nonneg a (one_plus_abs_pow_nonneg x (n - i - 1 + i)),
    },
    {
      exact abs_nonneg (y - x),
    },
  end

  lemma final (x y c : ℝ) (n : ℕ) (h₁ : 0 < c) (d : 0 < |y - x|) (h₂ : |y - x| < c / ((↑n + 2) * (↑n + 1) * (1 + |x|) ^ n)) :
    (↑n + 1) * (|y - x| * (↑n + 1) * (1 + |x|) ^ n) ≤ c :=
  begin
    have h₁' : 0 ≤ c, by linarith,
    have h₂' : |y - x| ≤ c / ((↑n + 2) * (↑n + 1) * (1 + |x|) ^ n), by linarith,
    have p := power_limit_denom_pos x n,
    have p' : 0 ≤ (↑n + 2) * (↑n + 1) * (1 + |x|) ^ n, by linarith,
    have : |y - x| ≤ c / ((↑n + 2) * (↑n + 1) * (1 + |x|) ^ n) ↔ |y - x| * ((↑n + 2) * (↑n + 1) * (1 + |x|) ^ n) ≤ c :=
    begin
      exact le_div_iff p,
    end,
    replace h₂' := this.mp h₂',
    rw [← mul_assoc, ← mul_assoc] at h₂',
    rw [← mul_assoc, ← mul_assoc, mul_comm (↑n + 1) (|y-x|)],
    have : (0 : ℝ) ≤ ↑n := nat.cast_nonneg n,
    have c : ↑n + (1 : ℝ) ≤ ↑n + 2, by linarith,
    have a' : 0 ≤ (↑n +(1 : ℝ)), by linarith,
    have a'' := one_plus_abs_pow_nonneg x n,
    have a''' := mul_nonneg a' a'',
    have b' : (0 : ℝ) ≤ ↑n + (2:ℝ), by linarith,

    have a : (↑n + (1 : ℝ)) * (1 + |x|) ^ n ≤ (↑n + (1 : ℝ)) * (1 + |x|) ^ n, by linarith,
    have b := mul_le_mul c a a''' b',
    have d': |y - x| ≤ |y - x|, by linarith,
    have d'' : (0 : ℝ) ≤ ↑n + 1, by linarith,
    have d''' := mul_nonneg d'' a''',
    have d := mul_le_mul d' b d''' (abs_nonneg (y-x)),
    linarith,
  end

  lemma ineq_inner_sum_2 (x : ℝ) (n : ℕ) ⦃c : ℝ⦄ (y : ℝ)
  (r : 0 < c)
  (p₁ : |y - x| < 1)
  (p₂ : |y - x| < c / ((↑n + 2) * (↑n + 1) * (1 + |x|) ^ n))
  (d : 0 < |y - x|) :
  |∑ (x_1 : ℕ) in
        range (n + 1),
        x ^ x_1 * (y - x) *
          ∑ (x_2 : ℕ) in
            range (n - x_1),
            y ^ (n - x_1 - 1 - x_2) * x ^ x_2| ≤
    c :=
  begin
    have h₁' := bound_xy_y x y p₁,
    replace h₁' : |y| ≤ 1 + |x|, by linarith,
    have a := function_bound_4 x y n h₁',
    have b := finset.sum_le_sum (ineq_constants x y n),
    have c' := finset.abs_sum_le_sum_abs (λ x_1, x ^ x_1 * (y - x) * ∑ (x_2 : ℕ) in range (n - x_1), y ^ (n - x_1 - 1 - x_2) * x ^ x_2) (range (n + 1)),

    simp at a,
    simp at b,
    simp at c',

    have d' : |∑ (x_1 : ℕ) in range (n + 1), x ^ x_1 * (y - x) * ∑ (x_2 : ℕ) in range (n - x_1), y ^ (n - x_1 - 1 - x_2) * x ^ x_2| ≤
      (↑n + 1) * (|y - x| * (↑n + 1) * (1 + |x|) ^ n), by linarith,
    have e := final x y c n r d p₂,
    linarith,
  end

  lemma inequality_chasing_deriv_of_power (x : ℝ) (n : ℕ) ⦃c : ℝ⦄ (y : ℝ)
  (r : 0 < c)
  (p : dist y x < min 1 (c / (((n+2) * (n+1) * (1 + |x|) ^ n)))) :
  |(y - x) * ∑ (x_1 : ℕ) in range (n + 1), y ^ (n - x_1) * x ^ x_1 +
        (x - y) * (x ^ n * ↑n + x ^ n)| ≤
    c * |y - x| :=
  begin
    unfold dist at p,
    rw lt_min_iff at p,
    have p₁ := p.1,
    have p₂ := p.2,
    clear p,
    rw ← neg_sub y x,
    rw neg_mul,
    rw ← sub_eq_add_neg ((y - x) * ∑ (x_1 : ℕ) in range (n + 1), y ^ (n - x_1) * x ^ x_1) ((y - x) * (x ^ n * ↑n + x ^ n)),
    rw ← mul_sub_left_distrib,
    rw abs_mul,
    rw mul_comm,

    have := bound_xy_y x y p₁,
    have : |y| ≤ 1 + |x|, by linarith,
    have a := sum_ineq x y c this n,
    clear this this,
    simp at a,
    have d := eq_zero_or_ne_zero (|y-x|),
    cases d,
    {
      repeat {rw d},
      rw [mul_zero, mul_zero],
    },
    {
      have := abs_nonneg (y-x),
      replace d := d.out,
      replace d : 0 < |y-x| := begin
        by_contra,
        apply d,
        linarith,
      end,
      rw mul_le_mul_right d,
      rw individual_to_sum x n,
      have b := sub_sum_eq_sum_sub x (n + 1) (λ (x_1 : ℕ), y ^ (n - x_1) * x ^ x_1) (λ (x_1 : ℕ), x ^ n),
      dsimp at b,
      rw ← b,
      clear b,
      have g := bound_xy_y x y p₁,
      have : |y| ≤ 1 + |x|, by linarith,
      have f := function_bound x y this n,
      clear g this,
      rw factor_out x y n,
      rw rw_inner_expr x y n,
      exact ineq_inner_sum_2 x n y r p₁ p₂ d,
    },
  end

  theorem deriv_of_power : ∀ (n : ℕ), ∀ (x : ℝ), has_deriv_at (x_to_n n) (x_to_n' n x) x :=
  begin
    intros n x,
    rw has_deriv_at,
    rw has_deriv_at_filter,
    rw has_fderiv_at_filter,
    dsimp,
    induction n with n ih,
    { simp, },
    {
      clear ih,
      induction n with n ih,
      { simp, },
      {
        rw x_to_n',
        dsimp,
        ring_nf,
        refine asymptotics.is_o_iff.mpr _,
        intros c r,
        refine eventually_nhds_iff_ball.mpr _,
        unfold ball,
        simp,
        use min 1 (c / ((n.succ+2) * (n.succ+1) * (1 + |x|) ^ n.succ)),
        split,
        {
          rw lt_min_iff,
          split,
          { norm_num, },
          {
            exact power_limit_div_pos x n.succ c r,
          }
        },
        {
          intros y p,
          rw [← add_assoc, tactic.ring.add_neg_eq_sub, x_to_n_sub_y_to_n_factor y x (n.succ+1)],
          rw [← tactic.ring.add_neg_eq_sub (-(x ^ n.succ * (↑n+1))), ← neg_add, neg_mul,
            mul_comm (x ^ (n.succ) * (↑n+1) + x ^ n.succ), ← neg_mul,
            mul_comm (x ^ n.succ * (↑n+1) + x ^ n.succ) x],
          rw [add_comm (-y * (x ^ n.succ * (↑n+1) + x ^ n.succ)), neg_mul, tactic.ring.add_neg_eq_sub,
            ← mul_sub_right_distrib],
          have q := abs_add ((y - x) * ∑ (x_1 : ℕ) in range (n + 1), y ^ (n - x_1) * x ^ x_1) ((x - y) * (x ^ n * ↑n + x ^ n)),
          simp,
          have := inequality_chasing_deriv_of_power x n.succ y r p,
          simp at this,
          linarith,
        }
      }
    }

  end
end power_rule


-- Just playing around with showing various equivalences of classical
-- assumptions in (intuitionist) natural deduction.

-- Particularly I take some rules of inference and either prove them
-- intuitionistically or show that they are equivalent to the law of the
-- excluded middle, which is a useful weapon to use against intuitionists
-- ("How can you possibly reject the law of the excluded middle when that's
--   equivalent to double negation elimination?????")

-- double negation elimination
def dne_t := ∀ p: Prop, ¬¬p → p
-- law of excluded middle
def lem_t := ∀ p: Prop, p ∨ ¬p
-- law of weak excluded middle. Strictly less informative than the full law.
-- See also https://ncatlab.org/nlab/show/weak+excluded+middle
def lwem_t := ∀ p: Prop, ¬p ∨ ¬¬p
-- de Morgan's laws
-- (three of which are intuitionistically valid! spot the odd one out)
def dml_nor_and_right_t := ∀ p q: Prop, ¬(p ∨ q) → ¬p ∧ ¬q
def dml_nor_and_left_t := ∀ p q: Prop, ¬p ∧ ¬q → ¬(p ∨ q)
def dml_nand_or_right_t := ∀ p q: Prop, ¬(p ∧ q) → ¬p ∨ ¬q
def dml_nand_or_left_t := ∀ p q: Prop, ¬p ∨ ¬q → ¬(p ∧ q)
-- proof by contradiction and contraposition
def pf_by_contrapos_t := ∀ p q: Prop, (¬q → ¬p) → (p → q)
def pf_by_contradict_t := ∀ p q: Prop, ¬(p ∧ ¬q) → (p → q)
-- equivalence of implication with a related disjunction
-- (the left implication is actually intuitionistically valid)
def disj_equiv_right_t := ∀ p q: Prop, (p → q) → q ∨ ¬p
def disj_equiv_left_t := ∀ p q: Prop, q ∨ ¬p → (p → q)
-- law of noncontradiction (intuitionistically valid)
def noncontradiction_t := ∀ p: Prop, ¬(p ∧ ¬p)
-- ridiculous classical theorem
def iff_assoc_t := ∀ p q r: Prop, ((p ↔ q) ↔ r) ↔ (p ↔ (q ↔ r))

-- Much of the reasoning here could be made more concise using tactics like
-- contradiction or cases, but my, admittedly perhaps shortsighted, vision is to
-- produce proofs that are as close as possible to natural deduction, so while
-- I'm using tactic mode for legibility/usability I'm imposing this slightly
-- weird ephemeral style.

-- For related reasons I prefer apply and.intro to split, etc.

theorem lem_impl_dne: lem_t → dne_t :=
begin
  assume hlem,
  intro p,
  assume hnnp,
  have hpnp := hlem p,
  apply or.elim hpnp, {
    intro p, from p,
  }, {
    intro hnp,
    apply false.elim, from hnnp hnp,
  },
end

theorem dne_impl_lem: dne_t → lem_t :=
begin
  assume hdne,
  intro p,
  suffices hnn: ¬¬(p ∨ ¬p), {
    from hdne (p ∨ ¬p) hnn,
  }, {
    assume hnpnp,
    have hnp: ¬p, {
      assume hp,
      have hpnp: p ∨ ¬p, {
        apply or.inl, from hp,
      },
      apply false.elim, from hnpnp hpnp,
    },
    have hpnp: p ∨ ¬p, {
      apply or.inr, from hnp,
    },
    apply false.elim, from hnpnp hpnp,
  },
end

theorem lem_impl_contrapos: lem_t → pf_by_contrapos_t :=
begin
  assume hlem,
  intros p q,
  assume hhnqnp,
  apply or.elim (hlem q), {
    intros hq _,
    from hq,
  }, {
    intros hnq hp,
    apply false.elim,
    from hhnqnp hnq hp,
  },
end

theorem contrapos_impl_lem: pf_by_contrapos_t → lem_t :=
begin
  assume hcpos,
  intro p,
  apply dne_impl_lem,
  intro q,
  assume hnnq,
  apply hcpos true q _ true.intro,
  assume hnq _,
  from hnnq hnq,
end

theorem lem_impl_contradict: lem_t → pf_by_contradict_t :=
begin
  assume hlem,
  intros p q,
  assume hnhpnq,
  apply or.elim (hlem q), {
    assume hq _,
    from hq,
  }, {
    assume hnq hp,
    apply false.elim,
    from hnhpnq (and.intro hp hnq),
  },
end

theorem contradict_impl_lem: pf_by_contradict_t → lem_t :=
begin
  assume hcontr,
  intro p,
  apply dne_impl_lem,
  intro q,
  assume hnnq,
  apply hcontr true q _ true.intro,
  assume hntnq,
  from hnnq (and.elim_right hntnq),
end

theorem lem_impl_disj_equiv_right: lem_t → disj_equiv_right_t :=
begin
  assume hlem,
  intros p q,
  assume hpq,
  apply or.elim (hlem p), {
    assume hp,
    from or.inl (hpq hp),
  }, {
    assume hnp,
    from or.inr hnp,
  },
end

theorem disj_equiv_right_impl_lem: disj_equiv_right_t → lem_t :=
begin
  assume hdjeq,
  intro p,
  apply hdjeq p p,
  assume hp, from hp,
end

theorem disj_equiv_left: disj_equiv_left_t :=
begin
  intros p q,
  assume hqnp,
  apply or.elim hqnp, {
    assume hq _, from hq,
  }, {
    assume hnp hp,
    apply false.elim, from hnp hp,
  },
end

theorem noncontradiction: noncontradiction_t :=
begin
  intro p,
  assume hpnp,
  have hp := hpnp.left,
  have hnp := hpnp.right,
  apply false.elim, from hnp hp,
end

theorem dml_nor_and_right: dml_nor_and_right_t :=
begin
  intros p q,
  assume hnpq,
  apply and.intro, {
    assume hp,
    have hpq: p ∨ q := or.inl hp,
    from hnpq hpq,
  }, {
    assume hq,
    have hpq: p ∨ q := or.inr hq,
    from hnpq hpq,
  },
end

theorem dml_nor_and_left: dml_nor_and_left_t :=
begin
  intros p q,
  assume hnpnq,
  have hnp := hnpnq.left,
  have hnq := hnpnq.right,
  assume hpq,
  apply or.elim hpq, {
    from hnp,
  }, {
    from hnq,
  },
end

theorem dml_nand_or_left: dml_nand_or_left_t :=
begin
  intros p q,
  assume hnpnq,
  assume hpq,
  have hp := and.elim_left hpq,
  have hq := and.elim_right hpq,
  apply or.elim hnpnq, {
    assume hnp,
    from hnp hp,
  }, {
    assume hnq,
    from hnq hq,
  },
end

-- this feels like a bit more of a case-bash than it needs to be
theorem lwem_impl_dml_nand_or_right: lwem_t → dml_nand_or_right_t :=
begin
  assume hlwem,
  intros p q,
  assume hnpq,
  apply or.elim (hlwem p), {
    assume hnp,
    from or.inl hnp,
  }, {
    assume hnnp,
    have hnq: ¬q, {
      assume hq,
      have hnp: ¬p, {
        assume hp,
        from hnpq (and.intro hp hq),
      },
      from hnnp hnp,
    },
    from or.inr hnq,
  },
end

theorem dml_nand_or_right_impl_lwem: dml_nand_or_right_t → lwem_t :=
begin
  assume hdml,
  intro p,
  from (hdml p ¬p) (noncontradiction p),
end

-- this can probably be done better. Currently a very mechanical
-- case-bash. In a sense it might be kind of inevitable if
-- you want to prove it naturally, since there are necessarily
-- a lot of constructors involved
-- It's kind of fun that basically there are only two cases
-- where the classical is needed
theorem lem_impl_iff_assoc: lem_t → iff_assoc_t :=
begin
  assume hlem,
  intros p q r,
  have hright: ∀ p q r: Prop, (p ↔ q ↔ r) → (p ↔ (q ↔ r)), {
    intros p q r,
    assume hpqr,
    apply iff.intro, {
      assume hp,
      apply iff.intro, {
        assume hq,
        apply hpqr.mp,
        apply iff.intro, {
          assume _,
          from hq,
        }, {
          assume _,
          from hp,
        },
      }, {
        assume hr,
        from (hpqr.mpr hr).mp hp,
      },
    }, {
      assume hqr,
      apply or.elim (hlem r), {
        assume hr,
        from (hpqr.mpr hr).mpr (hqr.mpr hr),
      }, {
        assume hnr,
        apply or.elim (hlem p), {
          assume hp,
          from hp,
        }, {
          assume hnp,
          -- forward contrapositives
          have hnq: ¬q, {
            assume hq,
            from hnr (hqr.mp hq),
          },
          have hnpq: ¬(p ↔ q), {
            assume hpq,
            from hnr (hpqr.mp hpq),
          },
          apply false.elim, apply hnpq,
          apply iff.intro, {
            assume hp,
            apply false.elim, from hnp hp,
          }, {
            assume hq,
            apply false.elim, from hnq hq,
          },
        },
      },
    },
  },
  apply iff.intro, {
    from hright p q r,
  }, {
    assume hpqr,
    from (hright _ _ _ ((hright _ _ _ hpqr.symm).symm)).symm,
  },
end

theorem iff_assoc_impl_lem: iff_assoc_t → lem_t :=
begin
  assume hiff,
  apply dne_impl_lem,
  intros p hnnp,
  -- this is exactly what we want to prove.
  -- now it's just a bit of case work to show equivalence
  have h := (hiff false false p).mpr,
  suffices hr: (false ↔ false ↔ p), {
    from hr.mp iff.rfl,
  }, {
    apply h,
    apply iff.intro, {
      from false.elim,
    }, {
      assume hnp,
      from hnnp hnp.mpr,
    },
  },
end


/-
Copyright (c) 2021 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser, Kevin Buzzard, Jujian Zhang
-/
import algebra.direct_sum.algebra
import algebra.direct_sum.internal
import algebra.direct_sum.ring
import group_theory.subgroup.basic

/-!
# Internally-graded algebras

This file defines the typeclass `graded_algebra 𝒜`, for working with an algebra `A` that is
internally graded by a collection of submodules `𝒜 : ι → submodule R A`.
See the docstring of that typeclass for more information.

## Main definitions

* `graded_algebra 𝒜`: the typeclass, which is a combination of `set_like.graded_monoid`, and
  a constructive version of `direct_sum.submodule_is_internal 𝒜`.
* `graded_algebra.decompose : A ≃ₐ[R] ⨁ i, 𝒜 i`, which breaks apart an element of the algebra into
  its constituent pieces.
* `graded_algebra.proj 𝒜 i` is the linear map from `A` to its degree `i : ι` component, such that
  `proj 𝒜 i x = decompose 𝒜 x i`.
* `graded_algebra.support 𝒜 r` is the `finset ι` containing the `i : ι` such that the degree `i`
  component of `r` is not zero.

## Implementation notes

For now, we do not have internally-graded semirings and internally-graded rings; these can be
represented with `𝒜 : ι → submodule ℕ A` and `𝒜 : ι → submodule ℤ A` respectively, since all
`semiring`s are ℕ-algebras via `algebra_nat`, and all `ring`s are `ℤ`-algebras via `algebra_int`.

## Tags

graded algebra, graded ring, graded semiring, decomposition
-/

open_locale direct_sum big_operators

section graded_algebra

variables {ι R A : Type*}
variables [decidable_eq ι] [add_comm_monoid ι] [comm_semiring R] [ring A] [algebra R A]
variables (𝒜 : ι → submodule R A)

/-- An internally-graded `R`-algebra `A` is one that can be decomposed into a collection
of `submodule R A`s indexed by `ι` such that the canonical map `A → ⨁ i, 𝒜 i` is bijective and
respects multiplication, i.e. the product of an element of degree `i` and an element of degree `j`
is an element of degree `i + j`.

Note that the fact that `A` is internally-graded, `graded_algebra 𝒜`, implies an externally-graded
algebra structure `direct_sum.galgebra R (λ i, ↥(𝒜 i))`, which in turn makes available an
`algebra R (⨁ i, 𝒜 i)` instance.
-/

class graded_algebra extends set_like.graded_monoid 𝒜 :=
(decompose' : A → ⨁ i, 𝒜 i)
(left_inv : function.left_inverse decompose' (direct_sum.submodule_coe 𝒜))
(right_inv : function.right_inverse decompose' (direct_sum.submodule_coe 𝒜))

lemma graded_algebra.is_internal [graded_algebra 𝒜] :
  direct_sum.submodule_is_internal 𝒜 :=
⟨graded_algebra.left_inv.injective, graded_algebra.right_inv.surjective⟩

variable [graded_algebra 𝒜]

/-- If `A` is graded by `ι` with degree `i` component `𝒜 i`, then it is isomorphic as
an algebra to a direct sum of components. -/
def graded_algebra.decompose : A ≃ₐ[R] ⨁ i, 𝒜 i := alg_equiv.symm
{ to_fun := direct_sum.submodule_coe_alg_hom 𝒜,
  inv_fun := graded_algebra.decompose',
  left_inv := graded_algebra.left_inv,
  right_inv := graded_algebra.right_inv,
  map_mul' := alg_hom.map_mul _,
  map_add' := alg_hom.map_add _,
  commutes' := alg_hom.commutes _ }

@[simp] lemma graded_algebra.decompose'_def :
  graded_algebra.decompose' = graded_algebra.decompose 𝒜 := rfl

@[simp] lemma graded_algebra.decompose_symm_of {i : ι} (x : 𝒜 i) :
  (graded_algebra.decompose 𝒜).symm (direct_sum.of _ i x) = x :=
direct_sum.submodule_coe_alg_hom_of 𝒜 _ _

/-- The projection maps of graded algebra-/
def graded_algebra.proj (𝒜 : ι → submodule R A) [graded_algebra 𝒜] (i : ι) : A →ₗ[R] A :=
(𝒜 i).subtype.comp $
  (dfinsupp.lapply i).comp $
  (graded_algebra.decompose 𝒜).to_alg_hom.to_linear_map

@[simp] lemma graded_algebra.proj_apply (i : ι) (r : A) :
  graded_algebra.proj 𝒜 i r = (graded_algebra.decompose 𝒜 r : ⨁ i, 𝒜 i) i := rfl

/-- The support of `r` is the `finset` where `proj R A i r ≠ 0 ↔ i ∈ r.support`-/
def graded_algebra.support [Π (i : ι) (x : 𝒜 i), decidable (x ≠ 0)]
  (r : A) : finset ι :=
(graded_algebra.decompose 𝒜 r).support

lemma graded_algebra.proj_recompose (a : ⨁ i, 𝒜 i) (i : ι) :
  graded_algebra.proj 𝒜 i ((graded_algebra.decompose 𝒜).symm a) =
  (graded_algebra.decompose 𝒜).symm (direct_sum.of _ i (a i)) :=
by rw [graded_algebra.proj_apply, graded_algebra.decompose_symm_of, alg_equiv.apply_symm_apply]

@[simp] lemma graded_algebra.decompose_coe {i : ι} (x : 𝒜 i) :
  graded_algebra.decompose 𝒜 x = direct_sum.of _ i x :=
by rw [←graded_algebra.decompose_symm_of, alg_equiv.apply_symm_apply]

lemma graded_algebra.decompose_of_mem {x : A} {i : ι} (hx : x ∈ 𝒜 i) :
  graded_algebra.decompose 𝒜 x = direct_sum.of _ i (⟨x, hx⟩ : 𝒜 i) :=
graded_algebra.decompose_coe _ ⟨x, hx⟩

lemma graded_algebra.decompose_of_mem_same {x : A} {i : ι} (hx : x ∈ 𝒜 i) :
  (graded_algebra.decompose 𝒜 x i : A) = x :=
by rw [graded_algebra.decompose_of_mem _ hx, direct_sum.of_eq_same, subtype.coe_mk]

lemma graded_algebra.decompose_of_mem_ne {x : A} {i j : ι} (hx : x ∈ 𝒜 i) (hij : i ≠ j):
  (graded_algebra.decompose 𝒜 x j : A) = 0 :=
by rw [graded_algebra.decompose_of_mem _ hx, direct_sum.of_eq_of_ne _ _ _ _ hij, submodule.coe_zero]

variable [Π (i : ι) (x : 𝒜 i), decidable (x ≠ 0)]

lemma graded_algebra.mem_support_iff
  (r : A) (i : ι) :
  i ∈ graded_algebra.support 𝒜 r ↔ graded_algebra.proj 𝒜 i r ≠ 0 :=
begin
  rw [graded_algebra.support, dfinsupp.mem_support_iff, graded_algebra.proj_apply],
  simp only [ne.def, submodule.coe_eq_zero],
end

lemma graded_algebra.sum_support_decompose (r : A) :
  ∑ i in graded_algebra.support 𝒜 r, (graded_algebra.decompose 𝒜 r i : A) = r :=
begin
  conv_rhs { rw [←(graded_algebra.decompose 𝒜).symm_apply_apply r,
    ←direct_sum.sum_support_of _ (graded_algebra.decompose 𝒜 r)] },
  rw [alg_equiv.map_sum, graded_algebra.support],
  simp_rw graded_algebra.decompose_symm_of,
end

end graded_algebra


import .logic

variable {α : Type}

def size : fm α → nat
| (fm.true α) := 1
| (fm.false α) := 1
| (fm.atom a) := 1
| (fm.and p q) := size p + size q + 1
| (fm.or p q) := size p + size q + 1
| (fm.not p) := size p + 1
| (fm.ex p) := size p + 1

notation p `≃` q := size p = size q
notation p `≺` q := size p < size q

#check nat.less_than_or_equal

lemma eq_zero_of_lt_one (n) : n < 1 → n = 0 := sorry
lemma size_pos (p : fm α) : size p > 0 := sorry
lemma not_pred_top (p : fm α) : ¬ (p ≺ (fm.true α)) := sorry
lemma not_pred_bot (p : fm α) : ¬ (p ≺ (fm.false α)) := sorry
lemma not_pred_atom (p : fm α) (a : α) : ¬ (p ≺ (fm.atom a)) := sorry
lemma pred_not (p q : fm α) : (p ≺ ¬' q) → (p ≃ q) ∨ (p ≺ q) := sorry
lemma pred_ex (p q : fm α) : (p ≺ ∃' q) → (p ≃ q) ∨ (p ≺ q) := sorry

lemma crec_on_aux {C : fm α → Prop} (H : ∀ p, (∀ (q : fm α), (q ≺ p) → C q) → C p) :
∀ (p q : fm α), (q ≺ p) → C q :=
λ p, fm.rec_on p
  (λ q Hq, false.rec _ (not_pred_top q Hq))
  (λ q Hq, false.rec _ (not_pred_bot q Hq))
  (λ a q Hq, false.rec _ (not_pred_atom q a Hq))
  (λ q r Hq Hr s Hs, _)
  _
  (λ q Hq r Hr,
    begin
      cases (pred_not r q Hr) with H1 H2, apply H,
      intros s Hs, apply Hq, rewrite (eq.symm H1),
      apply Hs, apply (Hq _ H2)
    end)
  (λ q Hq r Hr,
    begin
      cases (pred_ex r q Hr) with H1 H2, apply H,
      intros s Hs, apply Hq, rewrite (eq.symm H1),
      apply Hs, apply (Hq _ H2)
    end)

lemma crec_on {C : fm α → Prop} :
  (∀ p, (∀ (q : fm α), (q ≺ p) → C q) → C p) → ∀ (p : fm α), C p :=
by {intros H p, apply H, apply crec_on_aux, apply H}


import tactic
import data.nat.prime_norm_num
variables (P Q  : Prop)
variables (α β : Type)
variables (R S: ℕ → Prop)
open function nat

/-
# Quantifiers

# Existential ∃ tactics: use / cases

If our goal is `⊢ ∃(x:A), P` (where P may depend on x) and we know that `a : A` is a term
with the required property, then we can tell Lean to `use a` and our goal will change to `⊢ P`.
In fact if we can prove `P` using `refl` then Lean will do this automatically.

If we have `h : ∃(x:A), P` in the local context then  `cases h with x hx`
will give us `x : A` and `hx : P` in our local context.

[Notice how similar this is to how we used `cases h with hp hq` with `h : P ∧ Q`]

# Universal ∀ tactics: intro, apply, specialize

If we have a goal `⊢ ∀(n:ℕ), P n` then we can use `intro n` and the our goal will
change to `⊢ P n`. (For Lean a `∀` statement is basically a function.)

If we have `(h : ∀(x:A), P x)` and we want to use this hypotesis for a particular choice
of `x` then we can use `specialize h x` or (if goal is `⊢ P x`) just `apply h`

# New tactics for numerical/arithmetic proofs: norm_num and linarith

`norm_num` can prove results involving actual numerical expressions such as `4*3^2 < 50`

`linarith` can prove results involving linear arithmetic including inequalities.

# New tactics to help us make sense of definitions: unfold and dsimp

`unfold blah` will unfold a definition of `blah`in the goal.
Use unfold when you're not sure what a definition means.

`dsimp` will simplify the goal using definitions.
Use dsimp if the goal looks unreadable.  -/

-- 01
example : ∃(n:ℕ), n = 20 :=
begin
  use 20,
end

-- 02
example : ∃(n:ℕ), 200 < n:=
begin
  use 201,
  norm_num,
end

-- 03
example : ∃(P:Prop), P:=
begin
  sorry,
end

-- 04
example : ∃ (P:Prop), ¬ P → P:=
begin
  sorry,
end

-- 05
example (h: ∃(n:ℕ), n.prime ∧ 10 < n) : ∃(k:ℕ), k.prime ∧ 7 < k :=
begin
  cases h with n hn,
  cases hn with hp hv,
  use n,
  split, {exact hp},
  linarith, -- another tactic for proving results using linear arithmetic
end

-- 06 Hint : `even n` is defined to be `∃(r:ℕ), n = r + r`
example (n : ℕ) : even (4 * n):=
begin
  unfold  even,
  use (2*n),
  sorry,
end

-- 07
example (h: ∃(n:ℕ), even n ∧ even (n+1)) : ∃(k:ℕ), even (2*k + 1) :=
begin
  sorry,
end

-- 08
example : ∀ (n:ℕ), ∃ (k:ℕ), n < k:=
begin
  intro n, -- note the similarity to defining a function from ℕ
  sorry,
end

-- 09
example : ∀(n:ℕ), R n ∧ S n → ∃(k:ℕ), S k:=
begin
  sorry,
end

-- 10
example (f: ℕ → ℕ) (k : ℕ): surjective f → ∃(n:ℕ), f n = 2023^k :=
begin
  unfold surjective,
  sorry,
end

-- 11 Hint: if we have `h : ∀ (n : ℕ), f n = g n` then `rw (h n)` will replace `f n` by `g n` in the goal.
example (f g: ℕ → ℕ) (h : ∀ n, f n = g n):  injective f → injective g:=
begin
  sorry,
end

-- 12
example (f : ℕ → ℕ) (m n : ℕ): bijective f → f m = f n → m = n:=
begin
  sorry,
end

/-
If (a b: ℕ) then `a ∣ b` means `a divides b`, ie. `∃(c:ℕ), b = a*c`
Note that this symbol is `\|` not `|` which is obviously not the same...-/

-- 13
example (h : ∀n:ℕ, 3 ≤ n → 2 ∣ n → ¬n.prime ) :∀(k:ℕ), even k → 3 ≤ k → ¬ k.prime :=
begin
  intros k hk1 hk2,
  apply h,
  exact hk2,
  cases hk1 with r hr,
  sorry,
end

-- We can use `⟨n,h⟩` notation for `∃ n, h`
-- 14
example (n : ℕ)(h : n.prime) : ∃ (k : ℕ), k.prime:=
begin
  exact ⟨n,h⟩,
end

-- 15 what if we don't have the hypothesis `n.prime`
example  : ∃ (k : ℕ), k.prime:=
begin
  use 2,
  exact nat.prime_two, -- here we used `mathlib`
end

-- 16 To complete the next proof we need to show 2027 is prime. Luckily `norm_num` will do it.
example  : ∃ (k : ℕ), 2026 < k ∧ k.prime:=
begin
  use 2027,
  sorry,
end

/-
# New tactic: push_neg

Given a proposition with quantifiers involving a negation  `push_neg` will push the negation inside
the expression as far as possible

Like `rw` we can also use it to simplify terms in the local context eg `push_neg at h`  -/

-- 17
example : ¬ ∃ (n:ℕ), ∀ (k:ℕ), k < n:=
begin
  push_neg,
  sorry,
end

-- 18 Hint: If m,n ∈ ℕ then `max m n` is their maximum, `norm_num` may be useful again here
example : ∀ (m: ℕ), ∀ (n: ℕ), ∃ (k:ℕ), m ≤ k ∧ n ≤ k:=
begin
  sorry,
end

variable (pr : α → Prop)
/- A function from a type to Prop is usually called a `predicate`
   For example `nat.prime: ℕ → Prop` -/

-- 19
example : (∀ (a:α), pr a) ↔ ¬ ∃ (b:α), ¬ pr b:=
begin
  split,
  {
    sorry,
  },
  {
    sorry,
  }
end

/-
In the previous example we had a `split` and the two parts used almost the same
code. One way to do such proofs is to use the `try {code}` tactic, which tries the
`code` but doesn't complain if it fails.
-/
-- 20
example : (∀ (a:α), pr a) ↔ ¬ ∃ b:α, ¬ pr b:=
begin
  split; -- note `;`
  {
    intros h1,
    try {push_neg},  -- this succeeds the 1st time but silently fails the 2nd time
    try {push_neg at h1},  --this succeeds the 2nd time but silently fails the 1st time
    exact h1,
  },
end

-- 21
example : ∃ (n: ℕ), n + 2 = 5 ∧ 2 * n = 6:=
begin
  sorry,
end

-- 22 beware the divides `∣` symbol is not `|` but `\|`
example : ¬(∀ n: ℕ, n.prime ∨ (2 ∣ n)):=
begin
  sorry,
end

-- 23 Hint: even n := ∃(r:ℕ), n = r + r
example : ∀ (n m:ℕ), even n ∧ even m → even (n+m):=
begin
 sorry,
end

-- A relation r on ℕ
variables (r : ℕ → ℕ → Prop)
-- 24
example:  reflexive r → r 17 17:=
begin
  unfold reflexive,
  sorry,
end

-- 25
example : symmetric r →  r 13 2 → r 2 13:=
begin
  unfold symmetric,
  sorry,
end

-- 26
example (a b c : ℕ) : equivalence r → r a b →  r b c →  r a c:=
begin
  unfold equivalence transitive,
  sorry,
end

-- 27
example (a b c : ℕ) : equivalence r → r a b →  r b c →  r c a:=
begin
  sorry,
end

-- 28
example (h1 : symmetric r) (h2: ∃ (a b:ℕ), r a b ∧ a ≠ b) :
(∀ {m n: ℕ}, r m n → r n m → m = n) → false :=
begin
  sorry,
end

-- 29 Hint: dsimp may be useful here (you'll know when to use it!)
example : ∃! (n:ℕ), ∀ (k:ℕ), n ≤ k :=
begin
  sorry,
end

-- 30
example : ¬∃ (n:ℕ), ∀ (k:ℕ), k ≤ n :=
begin
  sorry,
end

/- If we want to define a function and then use it we use the `def` keyword

 We can define the relation `parity` on the integers ℤ
 `parity a b` holds iff `a + b` is even -/
def parity : ℤ → ℤ → Prop :=
begin
  intros a b,
  exact even (a+b),
end

-- Lets prove this is an equivalence relation
-- 31
example : equivalence parity :=
begin
  split,
  {
    sorry,
  },
  split,
  {
    sorry,
  },
  {
    sorry,
  },
end

-- The relation `r a b` iff `a = b mod m` iff `m ∣ (b-a)`
def mod (m: ℤ) : ℤ → ℤ → Prop :=
begin
  intros a b,
  exact m ∣ (b-a),
end

-- 32
example :∀ (m:ℤ), equivalence (mod m) :=
begin
  sorry,
end


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

! This file was ported from Lean 3 source module algebra.order.nonneg.floor
! leanprover-community/mathlib commit b3f4f007a962e3787aa0f3b5c7942a1317f7d88e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Order.Nonneg.Ring
import Mathlib.Algebra.Order.Archimedean

/-!
# Nonnegative elements are archimedean

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

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

## Main declarations

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

namespace Nonneg

variable {α : Type _}

instance archimedean [OrderedAddCommMonoid α] [Archimedean α] : Archimedean { x : α // 0 ≤ x } :=
  ⟨fun x y hy =>
    let ⟨n, hr⟩ := Archimedean.arch (x : α) (hy : (0 : α) < y)
    ⟨n, show (x : α) ≤ (n • y : { x : α // 0 ≤ x }) by simp [*, -nsmul_eq_mul, nsmul_coe]⟩⟩
#align nonneg.archimedean Nonneg.archimedean

instance floorSemiring [OrderedSemiring α] [FloorSemiring α] :
    FloorSemiring { r : α // 0 ≤ r } where
  floor a := ⌊(a : α)⌋₊
  ceil a := ⌈(a : α)⌉₊
  floor_of_neg ha := FloorSemiring.floor_of_neg ha
  gc_floor ha := FloorSemiring.gc_floor (Subtype.coe_le_coe.2 ha)
  gc_ceil a n := FloorSemiring.gc_ceil (a : α) n
#align nonneg.floor_semiring Nonneg.floorSemiring

@[norm_cast]
theorem nat_floor_coe [OrderedSemiring α] [FloorSemiring α] (a : { r : α // 0 ≤ r }) :
    ⌊(a : α)⌋₊ = ⌊a⌋₊ :=
  rfl
#align nonneg.nat_floor_coe Nonneg.nat_floor_coe

@[norm_cast]
theorem nat_ceil_coe [OrderedSemiring α] [FloorSemiring α] (a : { r : α // 0 ≤ r }) :
    ⌈(a : α)⌉₊ = ⌈a⌉₊ :=
  rfl
#align nonneg.nat_ceil_coe Nonneg.nat_ceil_coe


import analysis.normed_space.basic
import analysis.calculus.local_extr
import group_theory.semidirect_product
import analysis.calculus.lhopital

import math.deriv

universes u

/-!
# Lennard-Jones potential function
We define the properties of Lennard-Jonnes potential function that describes intermolecular
pair potentials in molecular simulations. The commonly used expression is: <br>
$$
E = 4ε  [(\frac{σ}{r})^{12} - (\frac{σ}{r})^6]
$$
where:
- `E` is the intermolecular potential between the two atoms or molecules
- `ε` is the well depth and a measure of how strongly the two particles attract each other
- `σ` is the distance at which the intermolecular potential between the two particles is zero
- `r` is the distance of separation between both particles

Here we use our own proof of `deriv` defined in `math.deriv` to show that Lennard-Jones potential has its minimum
at a distance of `r = r_min = 2^1/6 σ`, where the potential energy has the value `-ε`
-/

noncomputable theory

variables (ε minRadius: ℝ)

def LJ (ε minRadius r : ℝ)  :  ℝ :=
  let σ := (1 / 2 ^ (1 / 6:ℝ):ℝ)*minRadius in
  4*ε*(∥σ/r∥^(12)-∥σ/r∥^(6))

open real

lemma two_pow_one_div_six_pow_six_eq_two
:
(2 ^ (1 / 6:ℝ):ℝ) ^ (6) = (2:ℝ):=
begin
  have h : 0 ≤ (2:ℝ) := by simp,
  rw [← real.rpow_nat_cast, show ↑(6:ℕ) = (6:ℝ), by norm_num, ← real.rpow_mul h (1/6) 6],
  norm_num,
end

lemma two_pow_one_div_six_pow_twelve_eq_four
:
(2 ^ (1 / 6:ℝ):ℝ) ^ (12) = (4:ℝ):=
begin
  have h : 0 ≤ (2:ℝ) := by simp,
  rw [← real.rpow_nat_cast, show ↑(12:ℕ) = (12:ℝ), by norm_num, ← real.rpow_mul h (1/6) 12],
  norm_num,
end

lemma one_lt_six {α : Type*} [ordered_semiring α] [nontrivial α] : (1 : α) < 6 :=
begin
  rw show (6 : α) = 2*3, by norm_num,
  apply one_lt_mul one_lt_two.le,
  simp [bit1, zero_lt_two],
  finish,
end

lemma one_lt_twelve {α : Type*} [ordered_semiring α] [nontrivial α] : (1 : α) < 12 :=
begin
  rw show (12 : α) = 6*2, by norm_num,
  apply one_lt_mul (one_lt_six.le) (one_lt_two),
  finish,
  finish,
end

namespace LJ

theorem minRadius_rpow_div_const_pos : ∀ r y : ℝ, 0 < minRadius → 0 < r → 0 < y → 0 < minRadius ^ y / r:=
begin
  intros r y this that that2,
  rw [lt_div_iff that, zero_mul],
  apply zero_lt_rpow this that2,
end

theorem const_mul_minRadius_rpow_pos : ∀ r y : ℝ, 0 < minRadius → 0 < r → 0 < y → 0 < r*minRadius^y:=
begin
  intros r y this that that2,
  rw [zero_lt_mul_left that],
  apply zero_lt_rpow this that2,
end

lemma repulsive_2
(hminRadius : 0 < minRadius)
:
(λ r : ℝ, |minRadius/((2^(1/6:ℝ):ℝ)*r)|^(12))= λ r : ℝ, (|minRadius^(12)/4|)/|r^(12)|:=
begin
  funext,
  rw [← abs_pow, div_pow, mul_pow, two_pow_one_div_six_pow_twelve_eq_four, abs_div, abs_mul, abs_div],
  field_simp,
end

lemma repulsive_3
(hminRadius : 0 < minRadius)
:
(λ r, (|minRadius|/(|(2^(1/6:ℝ):ℝ)|*|r|))^(12))= λ r, (|minRadius^(12)/4|)/|r^(12)|:=
begin
  funext,
  rw [div_pow, mul_pow, ← abs_pow, ← abs_pow, two_pow_one_div_six_pow_twelve_eq_four, abs_div, div_div, ← abs_pow],
end

lemma repulsive_4
(hminRaiuds : 0 < minRadius)
:
(λ (r : ℝ), minRadius ^ 12 / (2 ^ (1 /6:ℝ) * r) ^ 12 ) = λ r, (minRadius^(12)/4)/r^(12):=
begin
  funext,
  rw [mul_pow, two_pow_one_div_six_pow_twelve_eq_four, div_div],
end

lemma attractive_2
(hminRadius : 0 < minRadius)
:
(λ r, |minRadius/((2^(1/6:ℝ):ℝ)*r)|^(6)) =λ r, (|minRadius^(6)/2|)/|r^(6)|:=
begin
  funext,
  rw [← abs_pow, div_pow, mul_pow, two_pow_one_div_six_pow_six_eq_two, abs_div, abs_mul, abs_div],
  field_simp,
end

lemma attractive_3
(hminRadius : 0 < minRadius)
:
(λ r, (|minRadius|/(|(2^(1/6:ℝ):ℝ)|*|r|))^(6)) = λ r, (|minRadius^(6)/2|)/|r^(6)|:=
begin
  funext,
  rw [div_pow, mul_pow, ← abs_pow, ← abs_pow, two_pow_one_div_six_pow_six_eq_two, abs_div, div_div, ← abs_pow],
end
lemma attractive_4
(hminRadius : 0 < minRadius)
:
(λ (r : ℝ), minRadius ^ 6 / (2 ^ (1 /6:ℝ) * r) ^ 6 ) = λ r, (minRadius^(6)/2)/r^(6):=
begin
  funext,
  rw [mul_pow, two_pow_one_div_six_pow_six_eq_two, div_div],
end

theorem deriv_of_LJ
(hminRadius : 0 < minRadius)
:
set.eq_on (deriv ( λ r, LJ ε minRadius r)) (λ r, 4*ε*((-3)*(minRadius^(12))/r^(13)-(-3)*(minRadius^(6))/r^(7))) ({r : ℝ | r = 0})ᶜ :=
begin
  simp_rw [LJ, real.norm_eq_abs, set.eq_on],
  intros x hx,
  simp at hx,
  simp [deriv_const_mul, mul_div_assoc],
  field_simp,
  left,
  rw [deriv_sub, repulsive_4 _ hminRadius, attractive_4 _ hminRadius, deriv_div_pow hx 12, deriv_div_pow hx 6],
  field_simp [sub_mul],
  norm_num,
  ring_exp!,
  iterate 2 {norm_num,},
  rw repulsive_4 _ hminRadius,
  apply differentiable_at_div_pow hx 12,
  rw attractive_4 _ hminRadius,
  apply differentiable_at_div_pow hx 6,
end

theorem has_deriv_at_of_LJ
{r : ℝ}
(hminRadius : 0 < minRadius)
(hr : r ≠ 0)
:
has_deriv_at ( λ r, LJ ε minRadius r) (4*ε*((-3)*(minRadius^(12))/r^(13)-(-3)*(minRadius^(6))/r^(7))) r:=
begin
  simp_rw [LJ, real.norm_eq_abs],
  apply has_deriv_at.const_mul (4*ε),
  rw [mul_comm, mul_div, mul_one],
  apply has_deriv_at.sub,
  simp,
  convert has_deriv_at_div_pow hr _ one_lt_twelve,
  field_simp [two_pow_one_div_six_pow_twelve_eq_four],
  ring_exp,
  simp,
  convert has_deriv_at_div_pow hr _ one_lt_six,
  field_simp [two_pow_one_div_six_pow_six_eq_two],
  ring_exp,
end

theorem has_deriv_at_of_LJ'
{r : ℝ}
:
has_deriv_at (λ (x : ℝ), 2 * x ^ 6 / minRadius ^ 6 - 4 * x ^ 12 / minRadius ^ 12) (6*r^5*(2/minRadius^6) - 12*r^11*(4/minRadius^12)) r:=
begin
  apply has_deriv_at.sub,
  simp [mul_comm (2 : ℝ) _, mul_div_assoc],
  apply has_deriv_at.mul_const,
  rw [show 5 = 6-1, by simp, show (6 : ℝ) = ↑6, by norm_num],
  apply has_deriv_at_pow,
  simp [mul_comm (4 : ℝ) _, mul_div_assoc],
  apply has_deriv_at.mul_const,
  rw [show 11 = 12-1, by simp, show (12 : ℝ) = ↑12, by norm_num],
  apply has_deriv_at_pow,
end

-- theorem deriv_of_LJ'
-- {x : ℝ}
-- (hminRadius : 0 < minRadius)
-- (hx : x ≠ 0)
-- :
-- (deriv_within  (λ r, LJ ε minRadius r)) ({r : ℝ | r = 0})ᶜ x = 4*ε*((-3)*(minRadius^(12))/x^(13)-(-3)*(minRadius^(6))/x^(7)) :=
-- begin
--   have hu : unique_diff_within_at ℝ ({r : ℝ | r = 0})ᶜ x,
--   { rw unique_diff_within_at_iff,
--     split,
--     simp [dense],
--     intro y,
--      },
--   simp [LJ, deriv_within_mul_const],
--   rw deriv_within_const_mul,
--   field_simp [← div_div],
--   rw deriv_within_sub,
-- end
-- theorem deriv2_of_LJ
-- (hminRadius : 0 < minRadius)
-- :
-- set.eq_on  (deriv^[2] (λ x, LJ ε minRadius x)) (λ x, ε*(156*minRadius^(12:ℝ)/x^(14:ℝ)-84*minRadius^(6:ℝ)/x^(8:ℝ))) ({r : ℝ | r = 0})ᶜ
-- :=
-- begin
--   rw set.eq_on,
--   intros x hx,
--   simp,
--   have h1 : set.eq_on (deriv (λ r, LJ ε minRadius r)) (λ r, 4*ε*((-3)*(minRadius^(12))/r^(13)-(-3)*(minRadius^(6))/r^(7))) ({r : ℝ | r = 0})ᶜ, from deriv_of_LJ _ _ hminRadius,
--   rw set.eq_on at h1,
--   specialize h1 hx,

-- end

-- theorem deriv_at_minEnergy
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- deriv (λ r, LJ ε minRadius r) minRadius = 0 :=
-- begin
--   rw deriv_of_LJ ε minRadius hrpos,
--   simp,
--   right,
--   ring_exp,
--   have h3 : minRadius ≠ 0 := by {specialize hrpos minRadius, linarith},
--   field_simp,
--   rw [neg_div, mul_comm _ (3:ℝ), mul_div_assoc, ← rpow_sub (hrpos minRadius), mul_comm _ (3:ℝ), mul_div_assoc, ← rpow_sub (hrpos minRadius)],
--   norm_num,

-- end

-- theorem deriv2_nonneg
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
--  0 ≤  (deriv^[2] (LJ ε minRadius) minRadius)  :=
-- begin
--   rw deriv2_of_LJ ε minRadius hrpos,
--   field_simp [hrpos ε, mul_div_assoc, ← rpow_sub (hrpos minRadius)],
--   norm_num,
--   apply decidable.mul_le_mul_of_nonneg_right,
--   norm_num,
--   apply real.rpow_nonneg_of_nonneg (le_of_lt (hrpos minRadius)),
-- end

-- def convex_zone (minRadius : ℝ) := set.Ioo (0:ℝ) ((13/7)^(1/6:ℝ)*minRadius)

-- lemma differentiable_at_LJ
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- ∀ x, differentiable_at ℝ (λ r, LJ ε minRadius r) x:=
-- begin
--   intro,
--   simp [LJ],
--   apply differentiable_at.const_mul _ (4*ε),
--   field_simp,
--   apply differentiable_at.sub,
--   rw repulsive_3 _ hrpos,
--   apply differentiable_at_inv_abs_rpow (hrpos x) (@minRadius_rpow_div_const_pos minRadius 4 12 (hrpos minRadius) zero_lt_four zero_lt_twelve) hrpos,
--   norm_num,
--   rw attractive_3 _ hrpos,
--   apply differentiable_at_inv_abs_rpow (hrpos x) (@minRadius_rpow_div_const_pos minRadius 2 6 (hrpos minRadius) zero_lt_two zero_lt_six) hrpos,
--   norm_num,
-- end

-- lemma differentiable_at_deriv_LJ
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- ∀ x, differentiable_at ℝ (deriv (λ r, LJ ε minRadius r)) x:=
-- begin
--   intro,
--   rw deriv_of_LJ _ _ hrpos,
--   apply differentiable_at.const_mul _ (4*ε),
--   field_simp,
--   apply differentiable_at.sub (differentiable_at_inv_rpow (hrpos x) _ _) (differentiable_at_inv_rpow (hrpos x) _ _),
--   iterate 2 {norm_num,},
-- end

-- lemma continuous_on_convex_zone
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- continuous_on (LJ ε minRadius) (convex_zone minRadius) :=
-- begin
--   apply @differentiable_on.continuous_on ℝ,
--   apply differentiable.differentiable_on,
--   rw differentiable,
--   exact (differentiable_at_LJ _ _ hrpos),
-- end

-- lemma differentiable_on_convex_zone
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- differentiable_on ℝ (LJ ε minRadius) (interior (convex_zone minRadius)):=
-- begin
--   simp [convex_zone],
--   apply differentiable.differentiable_on,
--   rw differentiable,
--   exact (differentiable_at_LJ _ _ hrpos),
-- end

-- lemma differentiable_of_deriv_on_convex_zone
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- differentiable_on ℝ (deriv (LJ ε minRadius)) (interior (convex_zone minRadius)):=
-- begin
--   simp [convex_zone],
--   apply differentiable.differentiable_on,
--   rw differentiable,
--   exact (differentiable_at_deriv_LJ _ _ hrpos),
-- end

-- lemma deriv2_of_convex_zone_nonneg
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- ∀ x ∈ (convex_zone minRadius), 0 ≤ (deriv^[2] (LJ ε minRadius) x):=
-- begin
--   intros x h,
--   let h1 := deriv2_of_LJ ε minRadius hrpos,
--   rw h1,
--   simp only [convex_zone] at h,
--   cases h with hl hu,
--   field_simp [hrpos ε],
--   rw [show x^(14:ℝ) = x^(6:ℝ)*x^(8:ℝ), by {rw ← real.rpow_add (hrpos x), norm_num}, show minRadius^(12:ℝ) = minRadius^(6:ℝ)*minRadius^(6:ℝ), by {rw ← real.rpow_add (hrpos minRadius), norm_num},
--   mul_div_assoc, mul_div_assoc, mul_div_mul_comm, ← mul_assoc, mul_le_mul_right (hrpos (minRadius^(6:ℝ)/x^(8:ℝ))), ← mul_div_assoc, le_div_iff (hrpos (x^(6:ℝ))),
--   ← le_div_iff' (hrpos (84:ℝ)), mul_comm (156:ℝ) _, mul_div_assoc],
--   norm_num,
--   apply le_of_lt,
--   rw [mul_comm, show (13/7)*minRadius^(6:ℝ) = ((13/7)^(1/6:ℝ)*minRadius)^(6:ℝ), by {rw [mul_rpow, ← rpow_mul], norm_num, norm_num, apply le_of_lt, apply zero_lt_rpow,
--   norm_num, norm_num, linarith [hrpos minRadius],}],
--   apply rpow_lt_rpow _ hu _,
--   linarith [hrpos x],
--   norm_num,
-- end

-- theorem convex_on
-- (hrpos : ∀ r : ℝ, 0 < r)
-- :
-- convex_on ℝ (convex_zone minRadius) (LJ ε minRadius) :=
-- begin
--   apply convex_on_of_deriv2_nonneg (convex_Ioo _ _) (continuous_on_convex_zone _ _ hrpos) (differentiable_on_convex_zone _ _ hrpos) (differentiable_of_deriv_on_convex_zone _ _ hrpos),
--   intros x h,
--   apply deriv2_of_convex_zone_nonneg _ _ hrpos,
--   simp [convex_zone],
--   simp at h,
--   exact h,
-- end

lemma sub_to_div
{f g : ℝ → ℝ}
(hf : ∀ x, f x ≠ 0)
(hg : ∀ x, g x ≠ 0)
: (λ x, f x - g x) = λ x, (1/g x-1/f x)/(1/(f x * g x)):=
begin
  field_simp,
  funext,
  field_simp [hf x, hg x],
  left,
  rw mul_comm,
end

lemma sub_eq_mul_one_sub_div
{f g : ℝ → ℝ}
(s : set ℝ)
(hf : ∀ x ∈ s, f x ≠ 0)
: set.eq_on (λ x, f x - g x) (λ x, f x * (1 - g x / f x)) s:=
begin
  rw set.eq_on,
  intros x hx,
  funext,
  rw mul_sub,
  field_simp [hf x hx, sub_mul],
  rw mul_comm,
end

lemma fun_sub
{f g : ℝ → ℝ}
:(λ x, f x - g x )= (λ x, f x) - λ x, g x := by {funext, simp}

lemma inv_of_pow_eq_neg_pow
{x : ℝ}
(n : ℕ)
: (x^n)⁻¹ = x^(-(n:ℤ)) :=
begin
  simp,
end

lemma inv_of_zpow_eq_neg_pow
{x : ℝ}
(z : ℤ)
: (x^z)⁻¹ = x^(-(z)) :=
begin
  simp,
end
-- lemma lhopital_tendsto_pow_div_pow_zero_at_zero
-- {n m : ℕ}
-- (hmn : n < m)
-- (hn : (0 : ℝ) < n)
-- : filter.tendsto (λ x : ℝ, x^m/x^n) (nhds_within 0 (set.Ioi 0)) (nhds 0) :=
-- begin
--   have hn' : 0 < n, by {norm_cast at hn, linarith [hn]},
--   have hm' : 0 < m, by exact lt_trans hn' hmn,
--   have hfa : filter.tendsto (λ x : ℝ, x^m) (nhds_within 0 (set.Ioi 0)) (nhds 0),
--   { rw show nhds (0 : ℝ) = nhds (0^m), by {simp, rw zero_pow hm'}, apply filter.tendsto.pow, rw nhds_within, apply filter.tendsto_inf_left, finish,},
--   have hga : filter.tendsto (λ x : ℝ, x^n) (nhds_within 0 (set.Ioi 0)) (nhds 0),
--   { rw show nhds (0 : ℝ) = nhds (0^n), by {simp, rw zero_pow hn'}, apply filter.tendsto.pow, rw nhds_within, apply filter.tendsto_inf_left, finish,},

-- end

lemma tendsto_at_zero_pow_seven_div_pow_one
: filter.tendsto (λ x : ℝ, x^7/x) (nhds_within 0 (set.Ioi 0)) (nhds 0):=
begin
  have h1 : (0 : ℝ) < 1, by norm_num,
  have h7 : (0 : ℝ) < 7, by norm_num,
  apply has_deriv_at.lhopital_zero_right_on_Ioo,
  exact zero_lt_two,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  conv {find (λ x : ℝ, x) {rw ← pow_one x},  },
  apply has_deriv_at_pow,
  intros x hx,
  norm_cast,
  rw ← ne.def,
  apply mul_ne_zero h1.ne',
  apply ne_of_gt (pow_pos hx.1 _),
  rw show nhds (0 : ℝ) = nhds (0^7), by {rw zero_pow, linarith [h7],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  rw show nhds (0 : ℝ) = nhds (0^1), by {rw zero_pow, linarith [h1],},
  conv {find (λ x : ℝ, x) {rw ← pow_one x},  },
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  norm_num,
  rw show nhds (0 : ℝ) = nhds(7*0), by simp,
  apply filter.tendsto.const_mul,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  rw show nhds (0 : ℝ) = nhds (0^6), by {rw zero_pow, norm_num,},
  apply filter.tendsto.pow,
  finish,
end

lemma tendsto_at_zero_pow_eight_div_pow_two
: filter.tendsto (λ x : ℝ, x^8/x^2) (nhds_within 0 (set.Ioi 0)) (nhds 0):=
begin
  have h2 : (0 : ℝ) < 2, by norm_num,
  have h8 : (0 : ℝ) < 8, by norm_num,
  apply has_deriv_at.lhopital_zero_right_on_Ioo,
  exact zero_lt_two,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  norm_cast,
  rw ← ne.def,
  apply mul_ne_zero h2.ne',
  apply ne_of_gt (pow_pos hx.1 _),
  rw show nhds (0 : ℝ) = nhds (0^8), by {rw zero_pow, linarith [h8],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  rw show nhds (0 : ℝ) = nhds (0^2), by {rw zero_pow, linarith [h2],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  norm_num,
  simp [mul_div_mul_comm],
  norm_num,
  rw show nhds (0 : ℝ) = nhds (4*0), by simp,
  apply filter.tendsto.const_mul,
  apply tendsto_at_zero_pow_seven_div_pow_one,
end

lemma tendsto_at_zero_pow_nine_div_pow_three
: filter.tendsto (λ x : ℝ, x^9/x^3) (nhds_within 0 (set.Ioi 0)) (nhds 0):=
begin
  have h3 : (0 : ℝ) < 3, by norm_num,
  have h9 : (0 : ℝ) < 9, by norm_num,
  apply has_deriv_at.lhopital_zero_right_on_Ioo,
  exact zero_lt_two,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  norm_cast,
  rw ← ne.def,
  apply mul_ne_zero h3.ne',
  apply ne_of_gt (pow_pos hx.1 _),
  rw show nhds (0 : ℝ) = nhds (0^9), by {rw zero_pow, linarith [h9],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  rw show nhds (0 : ℝ) = nhds (0^3), by {rw zero_pow, linarith [h3],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  norm_num,
  simp [mul_div_mul_comm],
  norm_num,
  rw show nhds (0 : ℝ) = nhds (3*0), by simp,
  apply filter.tendsto.const_mul,
  apply tendsto_at_zero_pow_eight_div_pow_two,
end

lemma tendsto_at_zero_pow_ten_div_pow_four
: filter.tendsto (λ x : ℝ, x^10/x^4) (nhds_within 0 (set.Ioi 0)) (nhds 0):=
begin
  have h4 : (0 : ℝ) < 4, by norm_num,
  have h10 : (0 : ℝ) < 10, by norm_num,
  apply has_deriv_at.lhopital_zero_right_on_Ioo,
  exact zero_lt_two,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  norm_cast,
  rw ← ne.def,
  apply mul_ne_zero h4.ne',
  apply ne_of_gt (pow_pos hx.1 _),
  rw show nhds (0 : ℝ) = nhds (0^10), by {rw zero_pow, linarith [h10],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  rw show nhds (0 : ℝ) = nhds (0^4), by {rw zero_pow, linarith [h4],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  norm_num,
  simp [mul_div_mul_comm],
  norm_num,
  rw show nhds (0 : ℝ) = nhds (5/2*0), by simp,
  apply filter.tendsto.const_mul,
  apply tendsto_at_zero_pow_nine_div_pow_three,
end

lemma tendsto_at_zero_pow_eleven_div_pow_five
: filter.tendsto (λ x : ℝ, x^11/x^5) (nhds_within 0 (set.Ioi 0)) (nhds 0):=
begin
  have h5 : (0 : ℝ) < 5, by norm_num,
  have h11 : (0 : ℝ) < 11, by norm_num,
  apply has_deriv_at.lhopital_zero_right_on_Ioo,
  exact zero_lt_two,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  norm_cast,
  rw ← ne.def,
  apply mul_ne_zero h5.ne',
  apply ne_of_gt (pow_pos hx.1 _),
  rw show nhds (0 : ℝ) = nhds (0^11), by {rw zero_pow, linarith [h11],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  rw show nhds (0 : ℝ) = nhds (0^5), by {rw zero_pow, linarith [h5],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  norm_num,
  simp [mul_div_mul_comm],
  rw show nhds (0 : ℝ) = nhds (11/5*0), by simp,
  apply filter.tendsto.const_mul,
  apply tendsto_at_zero_pow_ten_div_pow_four,
end

lemma tendsto_at_zero_pow_twelve_div_pow_six
: filter.tendsto (λ x : ℝ, x^12/x^6) (nhds_within 0 (set.Ioi 0)) (nhds 0) :=
 begin
  have h6 : (0 : ℝ) < 6, by norm_num,
  have h12 : (0 : ℝ) < 12, by norm_num,
  apply has_deriv_at.lhopital_zero_right_on_Ioo,
  exact zero_lt_two,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  apply has_deriv_at_pow,
  intros x hx,
  norm_cast,
  rw ← ne.def,
  apply mul_ne_zero h6.ne',
  apply ne_of_gt (pow_pos hx.1 _),
  rw show nhds (0 : ℝ) = nhds (0^12), by {rw zero_pow, linarith [h12],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  rw show nhds (0 : ℝ) = nhds (0^6), by {rw zero_pow, linarith [h6],},
  apply filter.tendsto.pow,
  rw nhds_within,
  apply filter.tendsto_inf_left,
  finish,
  norm_num,
  simp [mul_div_mul_comm],
  norm_num,
  rw show nhds (0 : ℝ) = nhds (2*0), by simp,
  apply filter.tendsto.const_mul,
  apply tendsto_at_zero_pow_eleven_div_pow_five,
end
lemma sub_to_div'
(hm :  0 < minRadius)
:set.eq_on (λ (x : ℝ),  minRadius ^ 12 / (2 ^ (6 : ℝ)⁻¹) ^ 12 / x ^ 12 - minRadius ^ 6 / (2 ^ (6 : ℝ)⁻¹) ^ 6 / x ^ 6)
(λ (x : ℝ),  minRadius ^ 12 / (2 ^ (6 : ℝ)⁻¹) ^ 12 / x ^ 12 * ( 1 - minRadius ^ 6 / (2 ^ (6 : ℝ)⁻¹) ^ 6 / x ^ 6 / (minRadius ^ 12 / (2 ^ (6 : ℝ)⁻¹) ^ 12 / x ^ 12)))
{r : ℝ | r ≠ 0}
:=
begin
  rw set.eq_on,
  intros x hx,
  simp at hx,
  rw ← ne.def at hx,
  rw [mul_sub, mul_one, ← mul_div_assoc, mul_div_cancel_left],
  field_simp,
  push_neg,
  split,
  exact ne_of_gt hm,
  apply mul_ne_zero _ (pow_ne_zero _ hx),
  apply pow_ne_zero,
  apply rpow_ne_zero (zero_lt_two),
  norm_num,
end
lemma sub_to_div''
(hm : 0 < minRadius)
(hx : ∀ x, x ∈ {r : ℝ | r ≠ 0})
:(λ (x : ℝ),  minRadius ^ 12 / (2 ^ (6 : ℝ)⁻¹) ^ 12 / x ^ 12 - minRadius ^ 6 / (2 ^ (6 : ℝ)⁻¹) ^ 6 / x ^ 6) =
(λ (x : ℝ),  minRadius ^ 12 / (2 ^ (6 : ℝ)⁻¹) ^ 12 / x ^ 12 * ( 1 - minRadius ^ 6 / (2 ^ (6 : ℝ)⁻¹) ^ 6 / x ^ 6 / (minRadius ^ 12 / (2 ^ (6 : ℝ)⁻¹) ^ 12 / x ^ 12)))
:=
begin
  let H := sub_to_div' _ hm,
  simp [set.eq_on] at H,
  funext,
  exact H (hx x),
end

/--The `Lennard-Jones` potential tendsto to positive infinity as radius approaches zero from the right-/
theorem tendsto_at_top_at_zero_radius
(hε : 0 < ε)
(hm :  0 < minRadius)
: filter.tendsto (λ r, LJ ε minRadius r) (nhds_within 0 (set.Ioi 0)) filter.at_top:=
begin
  simp only [LJ, norm_eq_abs],
  apply filter.tendsto.const_mul_at_top,
  simpa using hε,
  rw [mul_comm, mul_div, mul_one],
  simp [repulsive_2 minRadius hm, attractive_2 minRadius hm],
  rw sub_to_div'' _ hm hx,
  apply @filter.tendsto.at_top_mul _ _ _ _ _ _ _ _ (1 : ℝ) zero_lt_one,
  simp only [div_eq_mul_inv],
  apply filter.tendsto.const_mul_at_top,
  rw zero_lt_mul_left (pow_pos hm _),
  simp,
  apply pow_pos,
  apply zero_lt_rpow zero_lt_two _,
  norm_num,
  refine (filter.at_top_basis' 1).tendsto_right_iff.2 (λ b hb, _),
  have hb' : 0 < b := by positivity,
  filter_upwards [Ioc_mem_nhds_within_Ioi ⟨le_rfl, inv_pos.2 hb'⟩],
  intros a ha,
  simp,
  cases ha with hal hau,
  rw le_inv hal hb' at hau,
  rw ← inv_pow,
  transitivity,
  exact hau,
  conv { find (a⁻¹) {rw ← pow_one (a⁻¹),}, },
  apply pow_le_pow,
  apply le_trans hb hau,
  norm_num,
  field_simp [two_pow_one_div_six_pow_six_eq_two, two_pow_one_div_six_pow_twelve_eq_four],
  conv {find (_/_) {rw [show minRadius ^ 6 * (4 * x ^ 12) = x^12 * (minRadius^6*4), by ring_nf, show (2 * x ^ 6 * minRadius ^ 12) = x^6*(minRadius^12*2), by ring_nf],
                    simp [mul_div_mul_comm],
                    simp only [div_eq_mul_inv],},},
  rw show nhds (1 : ℝ) = nhds (1 - 0), by simp,
  apply filter.tendsto.const_sub (1 : ℝ),
  rw show nhds (0 : ℝ) = nhds (0 * ((minRadius ^ 6 * (minRadius ^ 12)⁻¹ * (4 * 2⁻¹)))), by simp,
  apply filter.tendsto.mul_const,
  simp [← div_eq_mul_inv],
  apply tendsto_at_zero_pow_twelve_div_pow_six,
end

end LJ


/-
Copyright (c) 2020 Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Kyle Miller
-/
import data.finset.basic
import combinatorics.simple_graph.basic
import tactic.omega
/-!
# Walks and paths
A walk in a simple graph is a finite sequence of adjacent vertices.  A path is a walk that visits
each vertex only once.
## Main definitions
* `simple_graph.walk` is the type of walks between pairs of vertices
* `simple_graph.walk.is_path` is a predicate that determines whether a walk is a path.
* `simple_graph.walk.to_path` constructs a path from a walk with the same endpoints.
* `simple_graph.exists_walk` is the walk-connectivity relation.
* `simple_graph.connected_components` is the quotient of the vertex type by walk-connectivity.
* `simple_graph.exists_walk_eq_exists_path` and `simple_graph.exists_walk_eq_eqv_gen` give that
  walk-connectivity, path-connectivity, and `eqv_gen G.adj` are the each the same relation.
* `simple_graph.walk.get_vert` parameterizes the vertices of a walk by natural numbers.
* `simple_graph.walk.is_path_iff` characterizes paths as walks for which `get_vert` is injective.
## Tags
walks, paths
-/

universes u

namespace simple_graph

variables {V : Type u} (G : simple_graph V)

-- lemma that says card of support is equal to length iff it's a path

/-- A walk is a sequence of incident edges in a graph, represented here as a sequence of adjacent
vertices. -/
inductive walk : V → V → Type u
| nil {u : V} : walk u u
| cons {u v w : V} (h : G.adj u v) (p : walk v w) : walk u w

attribute [refl] walk.nil

instance walk.inhabited (v : V) : inhabited (G.walk v v) := ⟨walk.nil⟩

namespace walk
variables {G}

/-- The length of a walk is the number of edges along it. -/
def length : Π {u v : V}, G.walk u v → ℕ
| _ _ nil := 0
| _ _ (cons _ q) := q.length.succ

/-- The concatenation of two compatible walks. -/
@[trans]
def concat : Π {u v w : V}, G.walk u v → G.walk v w → G.walk u w
| _ _ _ nil q := q
| _ _ _ (cons h p) q := cons h (concat p q)

/-- The concatenation of the reverse of the first walk with the second walk. -/
protected def reverse_aux : Π {u v w : V}, G.walk u v → G.walk u w → G.walk v w
| _ _ _ nil q := q
| _ _ _ (cons h p) q := reverse_aux p (cons (G.sym h) q)

/-- Reverse the orientation of a walk. -/
@[symm]
def reverse {u v : V} (w : G.walk u v) : G.walk v u := w.reverse_aux nil

/-- Get the nth vertex from a path, where if i is greater than the length of the path
the result is the endpoint of the path. -/
def get_vert : Π {u v : V} (p : G.walk u v) (i : ℕ), V
| u v nil _ := u
| u v (cons _ _) 0 := u
| u v (cons _ q) (i+1) := q.get_vert i

variables [decidable_eq V]

/-- The support of a walk is the finite set of vertices it visits. -/
def support : Π {u v : V}, G.walk u v → finset V
| u v nil := {u}
| u v (cons h p) := insert u p.support

-- ?????
def count (w : V) : Π {u v : V}, G.walk u v → ℕ
| u v nil := if u = w ∨ v = w then 1 else 0
| u v (cons h p) := if u = w then nat.succ (count p) else (count p)

/-- A path is a walk that visits each vertex at most once. -/
def is_path : Π {u v : V}, G.walk u v → Prop
| u v nil := true
| u v (cons h p) := p.is_path ∧ ¬ u ∈ p.support

end walk

/-- The relation on vertices of whether there exists a walk between them.
This is an equivalence relation. -/
def exists_walk : V → V → Prop := λ v w, nonempty (G.walk v w)

/-- The relation on vertices of whether there exists a path between them.
This is equal to `simple_graph.exists_walk`. -/
def exists_path [decidable_eq V]: V → V → Prop := λ v w, ∃ (p : G.walk v w), p.is_path

@[refl] lemma exists_walk.refl (v : V) : G.exists_walk v v :=
by { fsplit, refl, }

@[symm] lemma exists_walk.symm ⦃v w : V⦄ (hvw : G.exists_walk v w) : G.exists_walk w v :=
by { tactic.unfreeze_local_instances, cases hvw, use hvw.reverse, }

@[trans] lemma exists_walk.trans ⦃u v w : V⦄ (huv : G.exists_walk u v) (hvw : G.exists_walk v w) :
  G.exists_walk u w :=
by { tactic.unfreeze_local_instances, cases hvw, cases huv, use huv.concat hvw, }

lemma exists_walk.is_equivalence : equivalence G.exists_walk :=
mk_equivalence _ (exists_walk.refl G) (exists_walk.symm G) (exists_walk.trans G)

/-- The equivalence relation on vertices given by `simple_graph.exists_walk`. -/
def exists_walk.setoid : setoid V := setoid.mk _ (exists_walk.is_equivalence G)

/-- A connected component is an element of the quotient of the vertex type by the relation
`simple_graph.exists_walk`. -/
def connected_components := quotient (exists_walk.setoid G)

/-- A graph is connected if every vertex is connected to every other by a walk. -/
def is_connected : Prop := ∀ v w, exists_walk G v w

instance connected_components.inhabited [inhabited V]: inhabited G.connected_components :=
⟨@quotient.mk _ (exists_walk.setoid G) (default _)⟩

namespace walk

variables {G}

@[simp] lemma nil_length {u : V} : (nil : G.walk u u).length = 0 := rfl

@[simp] lemma cons_length {u v w : V} (h : G.adj u v) (p : G.walk v w) :
  (cons h p).length = p.length + 1 := rfl

@[simp] lemma nil_reverse {u : V} : (nil : G.walk u u).reverse = nil := rfl

lemma singleton_reverse {u v : V} (h : G.adj u v) :
  (cons h nil).reverse = cons (G.sym h) nil := rfl

@[simp] lemma cons_concat {u v w x : V} (h : G.adj u v) (p : G.walk v w) (q : G.walk w x) :
  (cons h p).concat q = cons h (p.concat q) := rfl

lemma cons_as_concat {u v w : V} (h : G.adj u v) (p : G.walk v w) :
  cons h p = concat (cons h nil) p := rfl

@[simp] lemma concat_nil : Π {u v : V} (p : G.walk u v), p.concat nil = p
| _ _ nil := rfl
| _ _ (cons h p) := by rw [cons_concat, concat_nil]

@[simp] lemma nil_concat {u v : V} (p : G.walk u v) : nil.concat p = p := rfl

lemma concat_assoc : Π {u v w x : V} (p : G.walk u v) (q : G.walk v w) (r : G.walk w x),
  p.concat (q.concat r) = (p.concat q).concat r
| _ _ _ _ nil _ _ := rfl
| _ _ _ _ (cons h p') q r := by { dsimp only [concat], rw concat_assoc, }

@[simp]
protected lemma reverse_aux_eq_reverse_concat {u v w : V} (p : G.walk u v) (q : G.walk u w) :
  p.reverse_aux q = p.reverse.concat q :=
begin
  induction p generalizing q w,
  { refl },
  { dsimp [walk.reverse_aux, walk.reverse],
    repeat { rw p_ih },
    rw ←concat_assoc,
    refl, }
end

@[simp] lemma concat_reverse {u v w : V} (p : G.walk u v) (q : G.walk v w) :
  (p.concat q).reverse = q.reverse.concat p.reverse :=
begin
  induction p generalizing q w,
  { simp },
  { dsimp only [cons_concat, reverse, walk.reverse_aux],
    simp only [p_ih, walk.reverse_aux_eq_reverse_concat, concat_nil],
    rw concat_assoc, }
end

@[simp] lemma cons_reverse {u v w : V} (h : G.adj u v) (p : G.walk v w) :
  (cons h p).reverse = p.reverse.concat (cons (G.sym h) nil) :=
begin
  dsimp [reverse, walk.reverse_aux],
  simp only [walk.reverse_aux_eq_reverse_concat, concat_nil],
end

@[simp] lemma reverse_reverse : Π {u v : V} (p : G.walk u v), p.reverse.reverse = p
| _ _ nil := rfl
| _ _ (cons h p) := by simp [reverse_reverse]

@[simp] lemma concat_length : Π {u v w : V} (p : G.walk u v) (q : G.walk v w),
  (p.concat q).length = p.length + q.length
| _ _ _ nil _ := by simp
| _ _ _ (cons _ p') _ := by simp [concat_length, add_left_comm, add_comm]

protected lemma reverse_aux_length {u v w : V} (p : G.walk u v) (q : G.walk u w) :
  (p.reverse_aux q).length = p.length + q.length :=
begin
  induction p,
  { dunfold walk.reverse_aux, simp, },
  { dunfold reverse_aux, rw p_ih, simp only [cons_length], ring, },
end

@[simp] lemma reverse_length {u v : V} (p : G.walk u v) : p.reverse.length = p.length :=
by { convert walk.reverse_aux_length p nil }

@[simp] lemma get_vert_0 {u v : V} (p : G.walk u v) : p.get_vert 0 = u :=
by { cases p; refl }

@[simp] lemma get_vert_n {u v : V} (p : G.walk u v) : p.get_vert p.length = v :=
by { induction p, refl, exact p_ih }

@[simp] lemma get_vert_nil {u : V} (i : ℕ) : (nil : G.walk u u).get_vert i = u :=
by cases i; simp [get_vert]

@[simp] lemma get_vert_cons {u v w : V} (h : G.adj u v) (p : G.walk v w) (i : ℕ) :
  (cons h p).get_vert (i + 1) = p.get_vert i := rfl

lemma concat_get_vert {u v w : V} (p : G.walk u v) (p' : G.walk v w) (i : ℕ) :
  (p.concat p').get_vert i = if i ≤ p.length then p.get_vert i else p'.get_vert (i - p.length) :=
begin
  induction p generalizing i,
  { simp only [get_vert_nil, nil_length, nil_concat, nat.sub_zero, nonpos_iff_eq_zero],
    split_ifs,
    { subst i, simp, },
    { refl } },
  { simp only [cons_length, cons_concat],
    cases i,
    { simp, },
    { simp [p_ih, nat.succ_le_succ_iff], } }
end

@[simp] lemma get_vert_reverse {u v : V} (p : G.walk u v) (i : ℕ) :
  p.reverse.get_vert i = p.get_vert (p.length - i) :=
begin
  induction i generalizing u v,
  { simp },
  { induction p,
    { simp },
    { simp only [cons_reverse, concat_get_vert, p_ih, i_ih,
                 cons_length, reverse_length, nat.succ_sub_succ_eq_sub, cons_reverse],
      split_ifs,
      { have h' : p_p.length - i_n = (p_p.length - i_n.succ).succ := by omega,
        simp [h'], },
      { have h' : p_p.length - i_n = 0 := by omega,
        have h'' : i_n.succ - p_p.length = (i_n - p_p.length).succ := by omega,
        simp [h', h''], } } }
end

protected lemma ext_aux : Π {u v : V} (n : ℕ) (p p' : G.walk u v)
  (h₁ : p.length = n) (h₁' : p'.length = n)
  (h₂ : ∀ i, i ≤ n → p.get_vert i = p'.get_vert i), p = p'
| _ _ 0 nil nil _ _ _ := rfl
| u v (n+1) (@cons _ _ _ w _ huw q) (@cons _ _ _ w' _ huw' q') h₁ h₁' h₂ :=
begin
  have hw : w = w',
  { specialize h₂ 1 (nat.le_add_left _ _),
    simpa using h₂, },
  subst w',
  congr,
  apply ext_aux n q q' (nat.succ.inj h₁) (nat.succ.inj h₁'),
  intros i h,
  exact h₂ i.succ (nat.succ_le_succ h),
end

@[ext]
lemma ext {u v : V} (p p' : G.walk u v)
  (h₁ : p.length = p'.length)
  (h₂ : ∀ i, i ≤ p.length → p.get_vert i = p'.get_vert i) :
  p = p' :=
walk.ext_aux p.length p p' rfl h₁.symm h₂

lemma adj_get_vert : Π {u v : V} (p : G.walk u v) (i : ℕ) (h : i < p.length),
  G.adj (p.get_vert i) (p.get_vert (i + 1))
| u v (cons huv p) 0 _ := by simp [huv]
| u v (cons huv p) (i+1) h := begin
  rw cons_length at h,
  simp only [get_vert_cons],
  exact adj_get_vert _ i (nat.lt_of_succ_lt_succ h),
end

section paths
variables [decidable_eq V]

@[simp] lemma nil_support {u : V} : (nil : G.walk u u).support = {u} := rfl

@[simp] lemma cons_support {u v w : V} (h : G.adj u v) (p : G.walk v w) :
  (cons h p).support = insert u p.support := rfl

@[simp] lemma start_mem_support {u v : V} (p : G.walk u v) : u ∈ p.support :=
by cases p; simp [support]

@[simp] lemma end_mem_support {u v : V} (p : G.walk u v) : v ∈ p.support :=
begin
  induction p,
  { simp [support], },
  { simp [support, p_ih], },
end

/-- Given a walk and a vertex in that walk, give a sub-walk starting at that vertex. -/
def subwalk_from : Π {u v w : V} (p : G.walk u v), w ∈ p.support → G.walk w v
| _ _ w nil h := by { rw [nil_support, finset.mem_singleton] at h, subst w }
| u v w (@cons _ _ _ x _ ha p) hs := begin
  rw [cons_support, finset.mem_insert] at hs,
  by_cases hw : w = u,
  { subst w, exact cons ha p },
  { have : w ∈ p.support, { cases hs, exact false.elim (hw hs), exact hs },
    exact p.subwalk_from this, },
end

lemma subwalk_from_path_is_path {u v w : V} (p : G.walk u v) (h : p.is_path) (hs : w ∈ p.support) :
  (p.subwalk_from hs).is_path :=
begin
  induction p,
  { rw [nil_support, finset.mem_singleton] at hs,
    subst w,
    trivial },
  { rw [cons_support, finset.mem_insert] at hs,
    dsimp only [is_path] at h,
    simp only [subwalk_from],
    split_ifs,
    { subst p_u,
      simp [is_path, h] },
    { cases hs,
      { exact false.elim (h_1 hs_1), },
      { exact p_ih h.1 _, } } },
end

/-- Given a walk, form a path between the same endpoints by splicing out unnecessary detours.
See `simple_graph.walk.to_path_is_path` -/
def to_path : Π {u v : V}, G.walk u v → G.walk u v
| u v nil := nil
| u v (@cons _ _ _ x _ ha p) :=
  let p' := p.to_path
  in if hs : u ∈ p'.support
     then p'.subwalk_from hs
     else cons ha p'

lemma subwalk_from_support_subset.aux (n' : ℕ) :
  Π {u v w : V} (p : G.walk u v) (hl : p.length = n') (h : w ∈ p.support),
    (p.subwalk_from h).support ⊆ p.support :=
begin
  refine nat.strong_induction_on n' (λ n ih, _),
  intros u v w p hl h,
  cases p,
  { rw [nil_support, finset.mem_singleton] at h,
    subst w,
    trivial, },
  { dsimp only [subwalk_from, support],
    rw [cons_support, finset.mem_insert] at h,
    split_ifs,
    { subst h_1,
      simp, },
    { have h' : w ∈ p_p.support := by cc,
      refine finset.subset.trans _ (finset.subset_insert _ _),
      convert_to (p_p.subwalk_from h').support ⊆ p_p.support,
      rw cons_length at hl,
      apply ih p_p.length (by linarith) _ rfl, }, },
end

lemma subwalk_from_support_subset {u v w : V} (p : G.walk u v) (h : w ∈ p.support) :
  (p.subwalk_from h).support ⊆ p.support :=
subwalk_from_support_subset.aux p.length p rfl h

lemma to_path_support_subset {u v : V} (p : G.walk u v) : p.to_path.support ⊆ p.support :=
begin
  induction p,
  { trivial, },
  { dsimp only [to_path, support],
    split_ifs,
    { refine finset.subset.trans (finset.subset.trans _ p_ih) (finset.subset_insert _ _),
      apply subwalk_from_support_subset, },
    { intro x,
      simp only [cons_support, finset.mem_insert],
      intro h,
      cases h,
      { subst p_u,
        exact or.inl rfl, },
      { exact or.inr (p_ih h_1), }, }, },
end

lemma to_path_is_path {u v : V} (p : G.walk u v) : p.to_path.is_path :=
begin
  induction p,
  { trivial, },
  { dsimp [to_path, is_path],
    split_ifs,
    { apply subwalk_from_path_is_path _ p_ih, },
    { use p_ih, }, },
end

@[simp] lemma nil_is_path {u : V} : (nil : G.walk u u).is_path := by trivial

@[simp] lemma singleton_is_path {u v : V} (h : G.adj u v) : (cons h nil).is_path :=
begin
  simp only [is_path, true_and, nil_support, finset.mem_singleton],
  intro h',
  subst u,
  exact G.loopless _ h,
end

@[simp]
lemma concat_support {u v w : V} (p : G.walk u v) (p' : G.walk v w) :
  (p.concat p').support = p.support ∪ p'.support :=
begin
  induction p generalizing p' w,
  { simp, },
  { simp only [finset.insert_union, cons_concat, cons_support],
    apply congr_arg _ (p_ih _), },
end

@[simp]
lemma reverse_support {u v : V} (p : G.walk u v) : p.reverse.support = p.support :=
begin
  induction p,
  { trivial, },
  { simp only [support, finset.insert_eq_of_mem, end_mem_support, concat_support,
               true_or, cons_reverse, finset.mem_union, finset.union_insert],
    rw [finset.union_comm, ←finset.insert_eq],
    apply congr_arg _ p_ih, },
end

lemma get_vert_mem_support {u v} (p : G.walk u v) (i : ℕ) : p.get_vert i ∈ p.support :=
begin
  induction p generalizing i,
  { simp, },
  { cases i,
    { simp, },
    { simp only [cons_support, finset.mem_insert, get_vert_cons],
      right,
      apply p_ih, }, },
end

lemma mem_support_exists_get_vert {u v w} (p : G.walk u v) (h : w ∈ p.support) :
  ∃ i, i ≤ p.length ∧ p.get_vert i = w :=
begin
  induction p,
  { rw [nil_support, finset.mem_singleton] at h,
    subst w,
    simp, },
  { rw [cons_support, finset.mem_insert] at h,
    cases h,
    { subst w, use 0, simp, },
    { specialize p_ih h,
      rcases p_ih with ⟨i, hi, hv⟩,
      use i+1, subst w, simp [hi],}, },
end

protected lemma is_path_iff.aux {u v : V} (p : G.walk u v) :
  p.is_path ↔ ∀ (i k : ℕ) (hi : i ≤ p.length) (hk : i + k ≤ p.length),
                 p.get_vert i = p.get_vert (i + k) → k = 0 :=
begin
  split,
  { intros hp i,
    induction i generalizing u v,
    { intro k,
      induction k generalizing u v,
      { simp },
      { simp only [forall_prop_of_true, get_vert_0, zero_le, zero_add],
        intros hk hu,
        cases p,
        { simpa using hk },
        { simp only [get_vert_cons] at hu,
          subst u,
          exact false.elim (hp.2 (get_vert_mem_support _ _)) } } },
    { cases p,
      { simp, },
      { simp only [cons_length, get_vert_cons],
        simp only [is_path] at hp,
        intros k hi hk,
        rw [add_comm, nat.add_succ], simp [get_vert],
        intro hv,
        rw [add_comm, nat.add_succ, add_comm] at hk,
        apply i_ih p_p hp.1 _ (nat.succ_le_succ_iff.mp hi) (nat.succ_le_succ_iff.mp hk),
        rw [hv, add_comm], } } },
  { intro h,
    induction p,
    { simp },
    { simp only [is_path],
      split,
      { apply p_ih,
        intros i k hi hk hv,
        apply h i.succ k (nat.succ_le_succ hi) _,
        rw [add_comm, nat.add_succ, add_comm],
        simp only [get_vert, hv, cons_length],
        rw [add_comm, nat.add_succ, add_comm],
        exact nat.succ_le_succ hk, },
      { intro hs,
        rcases mem_support_exists_get_vert _ hs with ⟨i, hl, rfl⟩,
        have h' := h 0 i.succ,
        simp only [cons_length, forall_prop_of_true, get_vert_0,
                   zero_le, zero_add, get_vert_cons] at h',
        specialize h' (nat.succ_le_succ hl),
        exact nat.succ_ne_zero _ h', } } }
end

lemma is_path_iff {u v : V} (p : G.walk u v) :
  p.is_path ↔ ∀ (i j : ℕ) (hi : i ≤ p.length) (hj : j ≤ p.length),
                 p.get_vert i = p.get_vert j → i = j :=
begin
  convert walk.is_path_iff.aux p,
  simp only [eq_iff_iff],
  split,
  { intros h i k hi hk hv,
    specialize h i (i+k) hi hk hv,
    linarith, },
  { intros h i j hi hj hv,
    wlog : i ≤ j using i j,
    specialize h i (j-i) hi,
    rw nat.add_sub_of_le case at h,
    specialize h hj hv,
    rw [(nat.sub_eq_iff_eq_add case).mp h, zero_add], }
end

lemma reverse_path {u v : V} (p : G.walk u v) (h : p.is_path) : p.reverse.is_path :=
begin
  rw is_path_iff,
  simp only [reverse_length, get_vert_reverse],
  intros i j hi hj hp,
  have hi' : p.length - i ≤ p.length := by omega,
  have hj' : p.length - j ≤ p.length := by omega,
  have h' := (is_path_iff p).mp h _ _ hi' hj' hp,
  omega,
end

lemma is_path_if_concat_is_path {u v w} (p : G.walk u v) (p' : G.walk v w)
  (h : (p.concat p').is_path) : p.is_path :=
begin
  rw is_path_iff at h,
  rw is_path_iff,
  simp only [concat_get_vert, concat_length] at h,
  intros i j hi hj hp,
  specialize h i j (by omega) (by omega),
  simp only [hi, hj, if_true] at h,
  exact h hp,
end

lemma get_vert_image {u v} (p : G.walk u v) :
  finset.image (λ i, p.get_vert i) (finset.range (p.length + 1)) = p.support :=
begin
  ext w,
  simp only [exists_prop, finset.mem_image, finset.mem_range],
  split,
  { simp only [and_imp, exists_imp_distrib],
    rintros i ih rfl,
    apply get_vert_mem_support, },
  { intro hw,
    rcases mem_support_exists_get_vert p hw with ⟨i, hi, hw'⟩,
    exact ⟨i, by linarith, hw'⟩, },
end

end paths

end walk

variables (G)

lemma exists_walk_eq_exists_path [decidable_eq V] : exists_walk G = exists_path G :=
begin
  ext u v,
  dsimp [exists_walk, exists_path],
  split,
  { intro p, cases p, fsplit, use p.to_path, apply walk.to_path_is_path, },
  { intro p, cases p, fsplit, use p_w, },
end

lemma exists_walk_eq_eqv_gen : exists_walk G = eqv_gen G.adj :=
begin
  ext v w,
  split,
  { intro h,
    cases h,
    induction h,
    { exact eqv_gen.refl _ },
    { exact eqv_gen.trans _ _ _ (eqv_gen.rel _ _ h_h) h_ih } },
  { intro h,
    induction h with x y h _ x y he h_ih x y z hxy hyz hxy_ih hyz_ih,
    { exact ⟨walk.cons h walk.nil⟩, },
    { refl, },
    { cases h_ih,
      exact ⟨h_ih.reverse⟩, },
    { cases hxy_ih,
      cases hyz_ih,
      exact ⟨hxy_ih.concat hyz_ih⟩, } }
end

end simple_graph