import data.nat.choose
import data.nat.prime
import data.nat.gcd
import data.list
import tactic.linarith

import data.set.finite
import data.multiset
import data.finset
import data.finsupp

open classical

local attribute [instance] nat.decidable_prime_1

/-
    https://en.wikipedia.org/wiki/Proof_of_Bertrand%27s_postulate
-/

/- Misc lemmas -/

lemma prod_le_prod : ∀ (l : list ℕ) (f g : ℕ → ℕ) ,
    (∀ (x:ℕ) , x ∈ l → f x ≤ g x) →
    (l.map f).prod ≤ (l.map g).prod := sorry

lemma sum_le_sum : ∀ (l : list ℕ) (f g : ℕ → ℕ) ,
    (∀ (x:ℕ) , x ∈ l → f x ≤ g x) →
    (l.map f).sum ≤ (l.map g).sum := sorry

/- Primes dividing choose -/

/- Define a floor log function for natural numbers.
    TODO does this already exist somewhere? -/
def log : ℕ → ℕ → ℕ
| 0 _ := 0
| 1 _ := 0
| _ 0 := 0
| (p+2) (n+1) := if (n+1) < (p+2) then 0 else (
    have h : (n+1)/(p+2) < n+1, from begin
        calc (n + 1) / (p + 2) ≤ (n+1) / 2 : sorry
        ... < (n+1) / 1 : sorry
        ... = n+1 : by simp
    end,
    log (p+2) ((n+1)/(p+2)) + 1
)

theorem pow_log_le : ∀ (a b : ℕ) ,
    a ^ (log a b) ≤ b := sorry

/- This is a closed form for the largest exponent
    r such that p^r divides (choose n k). -/
def lpdc (n:ℕ) (k:ℕ) (p:ℕ) :=
    ((list.range' 1 (log p n)).map
        (λ (i:ℕ) , if (k % (p^i)) + ((n-k) % (p^i)) ≥ p^i then 1 else 0)).sum

theorem p_pow_lpdc_dvd_choose : ∀ (n k p : ℕ) ,
    p ^ lpdc n k p ∣ choose n k := sorry

theorem exp_le_lpdc_of_dvd_choose : ∀ (n k p r : ℕ) ,
    p ^ r ∣ choose n k → r ≤ lpdc n k p := sorry

-- Which has this corollary:

lemma exp_le_log_of_dvd_choose : ∀ (n k p r : ℕ) ,
    p ^ r ∣ choose n k → r ≤ log p n :=
begin
    intros ,
    have h : r ≤ lpdc n k p := exp_le_lpdc_of_dvd_choose n k p r a,
    rw [lpdc] at h ,
    have h' := sum_le_sum (list.range' 1 (log p n))
        (λ (i : ℕ), ite (k % p ^ i + (n - k) % p ^ i ≥ p ^ i) 1 0)
        (λ (i : ℕ), 1)
        (begin
            intros , intros , simp , split_ifs , linarith , linarith ,
        end),
    simp at h' ,
    linarith ,
end

/- "lemma 1" -/

lemma four_n_bound : ∀ (n : ℕ) ,
    4 ^ n ≤ 2 * n * (choose (2*n) n) := sorry

/- "lemma 2" -/

lemma p_to_R_bound : ∀ (p:ℕ) (r:ℕ) (n:ℕ) ,
    nat.prime p →
    p ^ r ∣ (choose (2*n) n) →
    p ^ r ≤ 2*n :=
begin
    intros ,
    have h : r ≤ log p (2*n) := exp_le_log_of_dvd_choose (2*n) n p r a_1,
    calc p ^ r ≤ p ^ (log p (2*n)) : begin
        apply nat.pow_le_pow_of_le_right ,
        cases p ,
        have q : ¬ nat.prime 0 := dec_trivial , contradiction ,
        rw nat.succ_eq_add_one , linarith ,
        assumption ,
    end
        ... ≤ 2*n : begin
        apply pow_log_le ,
    end
end

/- "lemma 3" -/

theorem log_p_eq_1 : ∀ (p n) ,
    p ≥ 3 → 2*n / 3 < p → p ≤ n →
    log p (2*n) = 1 := sorry

lemma central_primes_do_not_divide : ∀ (p n : ℕ) ,
    nat.prime p →
    p ≠ 2 →
    2*n / 3 < p →
    p ≤ n →
    ¬ (p ∣ (choose (2*n) n)) :=
begin
    intros , by_contradiction ,
    have h := exp_le_lpdc_of_dvd_choose (2*n) n p 1 (begin
        simp , assumption ,
    end) ,

    have log_1 : log p (2*n) = 1 := begin
        apply log_p_eq_1 ,
        cases p ,
        have h : ¬ (nat.prime 0) := dec_trivial ,
        contradiction ,
        cases p ,
        have h : ¬ (nat.prime 1) := dec_trivial ,
        contradiction ,
        cases p ,
        contradiction ,
        rw nat.succ_eq_add_one ,
        rw nat.succ_eq_add_one ,
        rw nat.succ_eq_add_one ,
        linarith , assumption, assumption ,
    end,

    have j : lpdc (2 * n) n p = 0 := begin
        rw [lpdc] , rw log_1 , rw [list.range'] ,
        simp ,
        have l : ¬(n % p + (2 * n - n) % p ≥ p) := begin
            simp , rw two_mul , rw nat.add_sub_cancel ,
            rw <- two_mul ,
            have t : n = (n - p) + p := begin
                rw nat.sub_add_cancel , assumption ,
            end,
            rw t , simp ,
            have s : ((n - p) % p) ≤ n-p := begin
                apply nat.mod_le ,
            end,
            have r : 2*n < 3*p := begin
                rw [@nat.mul_comm 3 p] ,
                rw <- nat.div_lt_iff_lt_mul' , assumption ,
                norm_num ,
            end ,
            have q : 2*(n-p) < p := begin
                rw nat.mul_sub_left_distrib ,
                calc 2*n - 2*p < 3*p - 2*p :
                    begin
                        rw nat.sub_lt_sub_right_iff , assumption , linarith ,
                    end
                ... = (3-2)*p : by rw nat.mul_sub_right_distrib
                ... = p : by norm_num
            end,
            linarith ,
        end,
        simp [l] ,
    end,
    rw j at h , linarith ,
end

/- range-related lemmas -/

def range_to (n:ℕ) (m:ℕ) := list.range' n (m-(n-1))

theorem range_to_append : ∀ (n m k : ℕ) ,
    n ≤ m+1 → m ≤ k →
    range_to n m ++ range_to (m+1) k = range_to n k := sorry

/- "lemma 4" -/

def primorial (n : ℕ) : ℕ :=
    (list.filter nat.prime (range_to 1 n)).prod

lemma primorial_induction : ∀ (n : ℕ) ,
    primorial (n+1) = (if nat.prime (n+1) then primorial n * (n+1) else primorial n) := sorry

lemma zero_lt_primorial : ∀ (n : ℕ) ,
    0 < primorial n :=
begin
    intros , induction n,
    simp [primorial, list.filter, range_to, list.prod] , linarith ,
    rw primorial_induction ,
    split_ifs , rw mul_add , simp , linarith ,
    assumption ,
end

lemma primorial_eq_primorial_mul_primes : ∀ (n m : ℕ) ,
    m ≤ n →
    primorial n = primorial m *
        (list.filter nat.prime (range_to (m+1) n)).prod :=
begin
    intros ,
    rw [primorial, primorial] ,
        rw <- list.prod_append,
        rw <- list.filter_append ,
        rw <- range_to_append ,
        linarith, linarith ,
end

lemma primorial_ratio_eq_prod : ∀ (n m : ℕ) ,
    m ≤ n →
    primorial n / primorial m =
        (list.filter nat.prime (range_to (m+1) n)).prod :=
begin
    intros ,
    have h : primorial n = primorial m * (list.filter nat.prime (range_to (m+1) n)).prod := primorial_eq_primorial_mul_primes n m a,
    rw h , rw nat.mul_div_cancel_left ,
    apply zero_lt_primorial ,
end

lemma coprime_list : ∀ (m n p : ℕ) ,
    n < p →
    nat.prime p →
    nat.coprime (list.prod (list.filter nat.prime (range_to (m + 1) n))) p :=
begin
    intros , rw [range_to] , rw [nat.add_sub_cancel] ,
    by_cases (n < m),
    {
        have q : n-m = 0 := begin rw nat.sub_eq_zero_iff_le , apply nat.le_of_lt , assumption end,
        rw q , simp ,
    },
    have j : n - m + m = n := begin
        rw nat.sub_add_cancel , simp at h , assumption ,
    end,
    rw <- j at a,
    revert a , induction (n-m) ,
    {
        intros , simp ,
    },
    {
        intros , rw nat.succ_eq_add_one ,
        have k : list.range' (m + 1) (n_1 + 1) =
            list.range' (m + 1) n_1 ++
            list.range' (m + 1 + n_1) 1 := begin
            rw list.range'_append , rw nat.add_comm ,
            rw [@nat.add_comm 1 n_1] ,
        end,
        have k' : list.range' (m + 1 + n_1) 1 = [m + 1 + n_1] := by simp ,
        rw k' at k , rw k , rw list.filter_append , rw list.prod_append ,
        apply nat.coprime.mul ,
        apply ih , rw nat.succ_eq_add_one at a , linarith ,
        rw [list.filter] , split_ifs , simp ,
        rw [@nat.add_comm n_1 1] , rw <- nat.add_assoc ,
        rw nat.coprime_primes , rw nat.succ_eq_add_one at a ,
        by_contradiction , simp at a_2 , linarith ,
        assumption , assumption ,
        simp ,
    }
end

lemma prime_list_dvd : ∀ (n m k : ℕ) ,
    (∀ (p:ℕ) , nat.prime p → m+1 ≤ p → p ≤ n → p ∣ k) →
    (list.filter nat.prime (range_to (m+1) n)).prod ∣ k :=
begin
    intros, induction n ,
    {
        rw [range_to] , simp ,
    },
    {
        by_cases (m ≤ n_n) ,
        {
            have h : range_to (m + 1) (nat.succ n_n) = range_to (m+1) n_n ++ range_to (n_n + 1) (n_n + 1) := begin
                rw range_to_append , linarith , linarith ,
            end,
            rw h , rw list.filter_append , rw list.prod_append ,
            have h1 : range_to (n_n + 1) (n_n + 1) = [n_n + 1] := by simp [range_to] ,
            rw h1 , simp [list.filter] ,
            split_ifs ,
            {
                 have h2 : list.prod [n_n + 1] = n_n + 1 := by simp ,
                 rw h2 ,

                 have n_n_plus_1_dvd_k := a (n_n + 1) h_1 (by linarith) (begin
                    have t : nat.succ n_n = n_n + 1 := rfl , rw t ,
                 end),
                 apply nat.coprime.mul_dvd_of_dvd_of_dvd ,
                 apply coprime_list , linarith , assumption ,
                 apply n_ih , intros , apply a , assumption, assumption ,
                 have h_3 : nat.succ n_n = n_n + 1 := rfl , rw h_3 , linarith ,
                 assumption ,
            },
            {
                simp , apply n_ih , intros , apply a , assumption, assumption, have h_3 : nat.succ n_n = n_n + 1 := rfl , rw h_3 , linarith ,
            }
        },
        {
            have h_3 : nat.succ n_n = n_n + 1 := rfl , rw h_3 ,
            have t : n_n + 1 - m = 0 := begin
                rw nat.sub_eq_zero_iff_le , simp at h ,
                apply nat.le_of_lt_succ ,
                have h_4 : nat.succ m = m + 1 := rfl , rw h_4 ,
                linarith ,
            end,
            have h : range_to (m + 1) (n_n + 1) = [] := begin
                rw [range_to] , simp at h , rw nat.add_sub_cancel ,
                rw t , simp ,
            end,
            rw h , simp ,
        }
    }
end

lemma p_dvd_choose_2m_plus_1_of_ge_m_plus_2 : ∀ (m p : ℕ) ,
    nat.prime p →
    m + 2 ≤ p →
    p ≤ 2*m + 1 →
    p ∣ choose (2*m+1) (m+1) :=
begin
    intros ,
    have q := @choose_mul_fact_mul_fact (2*m + 1) (m + 1) (by linarith) ,
    have r : p ∣ nat.fact (2 * m + 1) := begin
            rw nat.prime.dvd_fact , assumption , assumption ,
        end,
    have r1 : ¬ (p ∣ nat.fact (m + 1)) := begin
            rw nat.prime.dvd_fact , simp , linarith , assumption ,
        end,
    have r2 : ¬ (p ∣ nat.fact (2 * m + 1 - (m + 1))) := begin
            rw nat.prime.dvd_fact , simp ,
            have h1 : 1 + 2 * m = (m + (m+1)) := by ring ,
            rw h1 , rw nat.add_sub_cancel , linarith ,
            assumption ,
        end,
    rw <- q at r ,
    rw nat.prime.dvd_mul at r ,
    cases r ,
    rw nat.prime.dvd_mul at r ,
    cases r , assumption , contradiction , assumption , contradiction ,
    assumption ,
end

lemma primorial_ratio_le_choose : ∀ (m : ℕ) ,
    m ≥ 2 →
    primorial (2*m + 1) / primorial (m+1) ≤ (choose (2*m + 1) (m+1)) :=
begin
    intros ,
    rw primorial_ratio_eq_prod ,
    apply nat.le_of_dvd ,
    {
        apply choose_pos , linarith ,
    },
    {
        apply prime_list_dvd , intros , apply p_dvd_choose_2m_plus_1_of_ge_m_plus_2 , assumption, linarith , assumption ,
    },
    {
        linarith ,
    },

end

lemma primorial_2m_plus_1_le_choose : ∀ (m : ℕ) ,
    m ≥ 2 →
    primorial (2*m + 1) ≤ primorial (m+1) * (choose (2*m + 1) (m+1)) :=
begin
    intros ,
    calc primorial (2*m + 1) =
         primorial (m+1) * (primorial (2*m + 1) / primorial (m+1)) :
    begin
        rw nat.mul_div_cancel' ,
        rw [primorial_eq_primorial_mul_primes (2*m+1) (m+1)] , simp ,
        linarith ,
    end
    ... ≤ primorial (m+1) * (choose (2*m + 1) (m+1)) :
    begin
        apply nat.mul_le_mul_left , apply primorial_ratio_le_choose , assumption ,
    end
end

lemma finset_sum_finset_range_eq_sum_range : ∀ (f: ℕ → ℕ) (n : ℕ) ,
    finset.sum (finset.range n) f = ((list.range n).map f).sum :=
begin
    intros , induction n , simp , rw[list.range] , rw [list.range_core] ,
    simp ,

    have h1 : list.sum (list.map f (list.range (nat.succ n_n)))
            = list.sum (list.map f (list.range n_n)) + f n_n :=
        begin
            rw [list.range_concat] , simp ,
        end,
    have h2 : finset.sum (finset.range (nat.succ n_n)) f
            = finset.sum (finset.range n_n) f + f n_n :=
        begin
            rw [finset.sum_range_succ] , rw nat.add_comm ,
        end,
    rw h1 , rw h2 , rw n_ih ,
end

lemma choose_2m_plus_1_le_power_2 : ∀ (m : ℕ) ,
    choose (2*m + 1) (m+1) ≤ 2^(2*m) :=
begin
    intros ,
    have t := (
        calc 2 * 2^(2*m) = 2^(2*m+1) :
            begin
                rw nat.pow_add , norm_num , ring ,
            end
        ... = ((1:ℕ) + (1:ℕ))^(2*m+1) : by simp
    ),
    have h := add_pow 1 1 (2*m+1) , simp at h ,
    simp at t , rw h at t , clear h,

    rw finset_sum_finset_range_eq_sum_range at t,

    have l : list.range (nat.succ (1 + 2 * m)) =
        list.range' 0 m ++
        list.range' m 2 ++
        list.range' (m+2) m :=
        begin
            have t1 : list.range' m 2 = list.range' (0+m) 2 := by simp , rw t1 ,
            rw [list.range_eq_range'] , rw [list.range'_append] ,
            have t2 : list.range' (m + 2) m = list.range' (0 + (2 + m)) m := by simp , rw t2 ,
            rw [list.range'_append] , rw [nat.succ_eq_add_one] ,
            have t3 : (1 + 2 * m + 1) = (m + (2 + m)) := by ring,
            rw t3 ,
        end ,
    rw l at t ,
    rw [list.map_append, list.map_append, list.sum_append, list.sum_append] at t ,
    have t' : 2 * 2 ^ (2 * m) ≥
        list.sum (list.map (choose (1 + 2 * m)) (list.range' m 2)) := by linarith ,
    clear t,
    simp at t' ,

    have q : choose (1 + 2 * m) m = choose (1 + 2 * m) (m+1) :=
        begin
            rw choose_eq_fact_div_fact , rw choose_eq_fact_div_fact ,
            have t1 : (1 + 2 * m) = m + (m+1) := by ring,
            rw t1 , rw nat.add_sub_cancel ,
            rw [@nat.add_comm m (m+1)] , rw nat.add_sub_cancel ,
            rw [@nat.mul_comm (nat.fact m) (nat.fact (m + 1))] ,
            linarith , linarith ,
        end ,
    rw q at t' ,
    have q' : choose (1 + 2 * m) (m + 1) + choose (1 + 2 * m) (m + 1) = 2 * choose (1 + 2 * m) (m + 1) := by ring ,
    rw q' at t' ,
    simp , linarith ,
end

lemma primorial_2m_plus_1_le_power_2 : ∀ (m : ℕ) ,
    m ≥ 2 →
    primorial (2*m + 1) ≤ primorial (m+1) * 2^(2*m) :=
begin
    intros ,
    calc primorial (2*m + 1) ≤
         primorial (m+1) * (choose (2*m + 1) (m+1)) :
            primorial_2m_plus_1_le_choose m a
    ... ≤ primorial (m+1) * 2^(2*m) :
        begin
            apply nat.mul_le_mul_left ,
            apply choose_2m_plus_1_le_power_2 ,
        end
end

lemma case_by_parity : ∀ (n : ℕ) ,
    (∃ (m:ℕ) , n = 2*m) ∨ (∃ (m:ℕ) , n = 2*m + 1) :=
begin
    intros , induction n ,
    left , existsi 0, norm_num ,
    cases n_ih ,
    cases n_ih , right , existsi n_ih_w , rw n_ih_h ,
    cases n_ih , left , existsi (n_ih_w + 1) , rw n_ih_h ,
    rw nat.succ_eq_add_one , ring ,
end

lemma le_2_pow_2_mul_2_mul_plus_1 : ∀ (m : ℕ) ,
    2 ^ (2 * (2 * m + 1)) ≤ 2 ^ (2 * (2 * (m + 1))) :=
begin
    intros ,
    apply nat.pow_le_pow_of_le_right , linarith ,
    linarith ,
end

lemma primorial_bound_aux : ∀ (bound n : ℕ) ,
    n < bound →
    n ≥ 3 →
    8 * primorial n < 2 ^ (2*n)
| 0 n :=
    begin
        intros , linarith ,
    end
| (bound+1) 0 := begin intros , linarith end
| (bound+1) 1 := begin intros , linarith end
| (bound+1) 2 := begin intros , linarith end
| (bound+1) 3 :=
    begin
        intros ,
        have p1 : ¬ (nat.prime 1) := dec_trivial ,
        have p2 : (nat.prime 2) := dec_trivial ,
        have p3 : (nat.prime 3) := dec_trivial ,
        rw [primorial, range_to] , norm_num ,
        simp [list.filter, list.prod, p1, p2, p3],
        norm_num,
    end
| (bound+1) 4 :=
    begin
        intros ,
        have p1 : ¬ (nat.prime 1) := dec_trivial ,
        have p2 : (nat.prime 2) := dec_trivial ,
        have p3 : (nat.prime 3) := dec_trivial ,
        have p4 : ¬ (nat.prime 4) := dec_trivial ,
        rw [primorial, range_to] , norm_num ,
        simp [list.filter, list.prod, p1, p2, p3, p4],
        norm_num,
    end
| (bound+1) (n+5) := begin
    intros ,
    cases (case_by_parity (n+5)) ,
    {
        cases h , rename h_w m , rw h_h at * ,
        cases m , simp at a , linarith ,
        have q : (nat.succ m) = (m+1) := rfl , rw q at * , clear q ,
        have m_ge_1 : m ≥ 1 := by linarith ,
        rw primorial ,
        rw [<- @range_to_append 1 (2*m + 1) (2*(m+1))] ,
        {
        rw list.filter_append ,
        have r : range_to (2 * m + 1 + 1) (2 * (m + 1)) = [2 * (m+1)] :=
            begin
                rw [range_to] , rw nat.add_sub_cancel ,
                have q' : 2 * (m + 1) = 1 + (2 * m + 1) := by ring ,
                have q : 2 * (m + 1) - (2 * m + 1) = 1 := begin
                    rw q', rw nat.add_sub_cancel ,
                end,
                rw q ,
                rw [list.range', list.range'],
                have l : (2 * m + 1 + 1) = 2 * (m + 1) := by ring,
                rw l ,
            end,
        rw r ,
        have s : list.filter nat.prime [2 * (m + 1)] = [] :=
            begin
                rw [list.filter] , split_ifs ,
                have t : ¬ (nat.prime (2 * (m+1))) := begin
                    apply nat.not_prime_mul, linarith , linarith ,
                end,
                contradiction ,
                rw [list.filter] ,
            end ,
        rw s ,
        simp ,
        have ih_bound : (2*m + 1 < bound) := by linarith ,
        have ih := primorial_bound_aux
            bound (2*m + 1) ih_bound (by linarith) ,
        rw [<- primorial] ,
        rw nat.add_comm ,
        calc 8 * primorial (2 * m + 1) < 2 ^ (2 * (2 * m + 1)) : ih
        ... ≤ 2 ^ (2 * (2 * (m + 1))) : (le_2_pow_2_mul_2_mul_plus_1 m)
        },
        linarith , linarith ,
    },
    {
        cases h , rename h_w m , rw h_h at * ,
        have m_ge_2 : m ≥ 2 := by linarith ,
        have h1 := primorial_2m_plus_1_le_power_2 m m_ge_2 ,
        have ih_bound : (m+1 < bound) := by linarith ,
        have h2 := primorial_bound_aux bound (m+1) ih_bound (by linarith) ,
        calc 8 * primorial (2 * m + 1) ≤ 8 * primorial (m + 1) * 2 ^ (2*m) :        begin
                rw nat.mul_assoc ,
                apply nat.mul_le_mul_left , assumption ,
              end
        ... < 2 ^ (2 * (m+1)) * 2 ^ (2*m) :
            begin
                rw mul_lt_mul_right , assumption ,
                apply nat.pos_pow_of_pos , linarith ,
            end
        ... = 2 ^ (2 * (2*m+1)) : begin
            rw <- nat.pow_add , ring ,
        end
    },
end

lemma primorial_bound : ∀ (n : ℕ) ,
    n ≥ 3 →
    8 * primorial n < 2 ^ (2*n) :=
begin
    intros ,
    apply (primorial_bound_aux (n+1) n) ,
    linarith , assumption ,
end

/- prime factorization -/

theorem factorize : ∀ (n m : ℕ) ,
    (∀ p , nat.prime p → p ∣ n → p ≤ m) →
    (∃ (r : ℕ → ℕ) , (∀ (p:ℕ) , nat.prime p → p ^ r p ∣ n) ∧
        n = (((range_to 1 m).filter nat.prime).map
            (λ p , p ^ (r p))).prod
    ) := sorry

lemma p_le_of_p_dvd_choose : ∀ (p n k : ℕ) ,
    k ≤ n → nat.prime p → p ∣ (choose n k) → p ≤ n :=
begin
    intros ,
    have q := @choose_mul_fact_mul_fact n k a,
    have r : (p ∣ nat.fact n) := begin
        cases a_2 , rw [a_2_h] at q , rw nat.mul_assoc at q ,
        rw nat.mul_assoc at q , rw <- q , apply dvd_mul_right ,
    end ,
    rw [nat.prime.dvd_fact] at r , assumption ,
    assumption ,
end

/- main result (bertrand's postulate for n ≥ 432 case) -/

lemma two_thirds_n_bound : ∀ (n : ℕ) ,
    n ≥ 468 →
    2 * n / 3 < 2 →
    false :=
begin
    intros ,
    have t := (
        calc 2 < 2 * 468 / 3 : by norm_num
        ... ≤ 2 * n / 3 : begin
            apply nat.div_le_div_right , linarith ,
        end
        ... < 2 : a_1
    ),
    linarith ,
end

lemma le_of_sqr_le_sqr : ∀ (n m : ℕ) ,
    n*n ≤ m*m → n ≤ m :=
begin
    intros , by_contradiction , simp at a_1 ,
    have h : m*m < n*n := nat.mul_self_lt_mul_self a_1 ,
    linarith ,
end

lemma le_div_mul : ∀ (n m : ℕ) ,
    m > 0 →
    n ≤ (n / m + 1) * m := sorry

lemma sqrt_le_2_3 : ∀ (n : ℕ) ,
    468 ≤ n → nat.sqrt (2 * n) ≤ 2 * n / 3 :=
begin
    intros , apply le_of_sqr_le_sqr ,
    have h : (2*n / 3 ≥ 4) := begin
            calc 2*n / 3 ≥ 2*468 / 3 :
                begin
                    apply nat.div_le_div_right ,
                    apply nat.mul_le_mul_left , assumption ,
                end
            ... ≥ 4 : by norm_num
        end ,
    calc nat.sqrt (2 * n) * nat.sqrt (2 * n) ≤ (2*n) : nat.sqrt_le (2*n)
    ... ≤ (2*n / 3 + 1) * 3 : le_div_mul (2*n) 3 (by linarith)
    ... = (2*n / 3) * 3 + 3 : begin rw add_mul , simp , end
    ... ≤ (2*n / 3) * 3 + (2*n / 3) : by linarith
    ... = (2*n / 3) * 4 :
            begin
                have q : 4 = 3+1 := by norm_num , rw q ,
                rw mul_add, simp ,
            end
    ... ≤ (2 * n / 3) * (2 * n / 3) :
            begin
                apply nat.mul_le_mul_left , assumption ,
            end
end

lemma p_le_2n_over_3 : ∀ (n p : ℕ) ,
    (∀ (x : ℕ), nat.prime x → n < x → 2 * n < x) →
    n ≥ 468 →
    nat.prime p →
    p ∣ (choose (2*n) n) →
    p ≤ (2*n / 3) :=
begin
    intros ,
    by_cases (p ≤ 2*n / 3) ,
    {
        assumption ,
    },
    simp at h , rename h p_bound ,
    by_cases (p ≤ n) ,
    {
        have q := central_primes_do_not_divide p n a_2 (begin
            /- show p > 2 -/
            by_contradiction , simp at a_4 , rw a_4 at * ,
            apply two_thirds_n_bound , assumption, assumption,
        end) p_bound h ,
        contradiction ,
    },
    simp at h , clear p_bound , rename h p_bound ,
    {
        have q := a p a_2 p_bound ,
        have r := p_le_of_p_dvd_choose p (2*n) n (by linarith) a_2 a_3 ,
        linarith ,
    },
end

lemma log_le_1_of_gt_sqrt : ∀ (n p : ℕ) ,
    p > nat.sqrt n → log p n ≤ 1 := sorry

lemma p_at_most_1_in_choose_of_gt_sqrt : ∀ (p n r : ℕ) ,
    nat.prime p →
    p > nat.sqrt (2*n) →
    p ^ r ∣ choose (2*n) n →
    r ≤ 1 :=
begin
    intros ,
    have h : r ≤ log p (2*n) := exp_le_log_of_dvd_choose (2*n) n p r a_2 ,
    have j : log p (2*n) ≤ 1 := log_le_1_of_gt_sqrt (2*n) p a_1 ,
    exact (nat.le_trans h j) ,
end

lemma factorize_choose_2n_n : ∀ (n : ℕ) ,
    (∀ (x : ℕ), nat.prime x → n < x → 2 * n < x) →
    n ≥ 468 →
    ∃ (r : ℕ → ℕ) ,
    (∀ (p:ℕ) , nat.prime p → p ^ r p ∣ choose (2*n) n) ∧
    (choose (2*n) n =
        (((range_to 1 (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod) :=
begin
    intros ,
    have f := factorize (choose (2*n) n) (2*n/3) (begin
        intros , apply p_le_2n_over_3 , assumption, assumption, assumption,
        assumption ,
    end),
    cases f , rename f_w r , cases f_h , rename f_h_left dvd_condition ,
    rename f_h_right prod_condition ,
    existsi r ,
    split , assumption ,
    rw prod_condition ,
end

lemma prod_repeat_eq_pow : ∀ (a b : ℕ) ,
    list.prod (list.repeat a b) = a ^ b :=
begin
    intros ,
    induction b ,
    simp , simp ,
    rw nat.pow_succ , rw nat.mul_comm ,
end

lemma length_filter_le_length {α : Type} (p : α → Prop) [decidable_pred p] : ∀ (l : list α) ,
    list.length (l.filter p) ≤ list.length l :=
begin
    intros ,
    induction l , simp , simp , rw [list.filter] , split_ifs ,
    simp , assumption , linarith ,
end

lemma prime_bounds_1 : ∀ (n : ℕ) (r : ℕ → ℕ),
    (∀ p , nat.prime p → p ^ r p ∣ (choose (2*n) n)) →
    (((range_to 1 (nat.sqrt (2*n))).filter nat.prime).map
                (λ p , p ^ (r p))).prod
    ≤ (2*n) ^ (nat.sqrt (2*n)) :=
begin
    intros ,
    by_cases (n ≥ 1) ,
    {
        have h := prod_le_prod
            ((range_to 1 (nat.sqrt (2*n))).filter nat.prime)
            (λ p , p ^ (r p))
            (λ p , 2 * n)
            (begin
                intros, simp ,
                simp at a_1 , cases a_1 ,
                apply p_to_R_bound , assumption , apply a , assumption,
            end),
        exact (nat.le_trans h (begin
            rw list.map_const , rw prod_repeat_eq_pow ,
            apply nat.pow_le_pow_of_le_right,
            linarith ,
            have l : (nat.sqrt (2 * n)) = list.length (range_to 1 (nat.sqrt (2 * n))) := begin
                rw [range_to] , rw [list.length_range'] , ring ,
            end,
            have len_le := length_filter_le_length nat.prime (range_to 1 (nat.sqrt (2 * n))) ,
            rw <- l at len_le ,
            assumption ,
        end)),
    },
    {
        simp at h ,
        have t : (nat.sqrt 0 = 0) := rfl ,
        rw h , norm_num , rw [range_to] , rw t , simp ,
    }
end

lemma prime_bounds_2 : ∀ (n : ℕ) (r : ℕ → ℕ),
    n ≥ 468 →
    (∀ p , nat.prime p → p ^ r p ∣ (choose (2*n) n)) →
    (((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod
    ≤ primorial (2*n / 3) :=
begin
    intros ,
    have h := prod_le_prod
        ((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime)
        (λ p , p ^ (r p))
        id
        (begin
            intros , simp at a_2, rw [range_to] at a_2 , simp at a_2 ,
            cases a_2 , cases a_2_left ,
            simp ,
            have t : x ^ r x ≤ x ^ 1 := begin
                apply nat.pow_le_pow_of_le_right , linarith ,
                apply p_at_most_1_in_choose_of_gt_sqrt x n (r x) ,
                assumption , linarith , apply a_1 , assumption ,
            end,
            simp at t,
            assumption ,
        end),
    exact (nat.le_trans h (begin
        rw list.map_id ,
        rw primorial ,
        have rsplit : range_to 1 (2 * n / 3) =
            range_to 1 (nat.sqrt (2 * n)) ++
            range_to (nat.sqrt (2 * n) + 1) (2 * n / 3) :=
                begin
                    rw range_to_append , linarith ,
                    apply sqrt_le_2_3 , assumption ,
                end,
        rw rsplit ,
        rw list.filter_append ,
        rw list.prod_append ,
        calc
            list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) =
                1 * list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) :
                begin
                    ring ,
                end
        ... ≤
                primorial (nat.sqrt (2 * n)) *
                list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) :
                begin
                    apply nat.mul_le_mul_right,
                    apply nat.le_of_lt_succ ,
                    have x : nat.succ (primorial (nat.sqrt (2 * n))) = primorial (nat.sqrt (2 * n)) + 1 := rfl ,
                    rw x ,
                    have t := zero_lt_primorial (nat.sqrt (2 * n)) ,
                    linarith ,
                end
        ... =
                list.prod (list.filter nat.prime (range_to 1 (nat.sqrt (2 * n)))) *
                list.prod (list.filter nat.prime (range_to (nat.sqrt (2 * n) + 1) (2 * n / 3))) :
                begin
                    rw [primorial] ,
                end
    end))
end

lemma main_bound : ∀ (n : ℕ) ,
    n ≥ 468 →
    2*n * ((2*n) ^ (nat.sqrt (2*n))) * (4 ^ (2*n/3)) < 4^n := sorry

lemma b_lt_c_of_ab_lt_c : ∀ (a b c : ℕ) ,
    a > 0 →
    a*b < c → b < c :=
begin
    intros ,
    calc b = 1*b : by simp
    ... ≤ a*b :
        begin
            apply nat.mul_le_mul_right ,
            cases a , linarith , rw nat.succ_eq_add_one , linarith ,
        end
    ... < c : by assumption
end

/- copied from assignment 3, surprised I couldn't find this built-in? -/

theorem e1 {α : Type} : ∀ (p : α → Prop) , (¬ ∃ x, p x) → ∀ x, ¬ p x :=
  assume p : α → Prop ,
  assume h : (¬ ∃ x, p x) ,
  assume x : α ,
  not.intro (
    assume g : p x ,
    absurd (exists.intro x g) h
  )

theorem exists_of_not_forall {α : Type} : ∀ (p : α → Prop) , (¬ ∀ x, ¬ p x) → ∃ x, p x :=
  assume p : α → Prop ,
  assume g : (¬ ∀ x, ¬ p x) ,
  or.elim (em (∃ x, p x))
  (
    assume h : (∃ x, p x) ,
    h
  )
  (
    assume h : ¬ (∃ x, p x) ,
    absurd (e1 p h) g
  )

lemma bertrands_postulate_main : ∀ (n : ℕ) ,
    n ≥ 468 →
    ∃ p , nat.prime p ∧ n < p ∧ p ≤ 2*n :=
begin
    intros ,
    apply exists_of_not_forall ,
    by_contradiction ,
    simp at a_1 ,

    have h := factorize_choose_2n_n n a_1 a ,
    cases h , rename h_w r , cases h_h ,
    rename h_h_left r_divides ,
    rename h_h_right choose_2n_n_factorization ,

    have t := (
    calc 4^n ≤ 2*n * (choose (2*n) n) : four_n_bound n
    ... = 2*n * (((range_to 1 (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod : begin
                     rw <- choose_2n_n_factorization,
                end
    ... = 2*n *
            (((range_to 1 (nat.sqrt (2*n))).filter nat.prime).map
                (λ p , p ^ (r p))).prod *
            (((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod :
            begin
                rw [@nat.mul_assoc (2*n) _ _] ,
                rw <- list.prod_append ,
                rw <- list.map_append ,
                rw <- list.filter_append ,
                rw <- range_to_append ,
                linarith ,
                apply sqrt_le_2_3 , assumption ,
            end
    ... ≤ 2*n *
            (2*n) ^ (nat.sqrt (2*n)) *
            (((range_to (nat.sqrt (2*n) + 1) (2*n/3)).filter nat.prime).map
                (λ p , p ^ (r p))).prod :
            begin
                apply nat.mul_le_mul_right ,
                apply nat.mul_le_mul_left ,
                exact (prime_bounds_1 n r r_divides) ,
            end
    ... ≤ 2*n *
            (2*n) ^ (nat.sqrt (2*n)) *
            primorial (2*n / 3) :
            begin
                apply nat.mul_le_mul_left ,
                exact (prime_bounds_2 n r a r_divides) ,
            end
    ... ≤ 2*n *
            (2*n) ^ (nat.sqrt (2*n)) *
            4 ^ (2*n/3) :
            begin
                have q := (calc
                        (3:ℕ) ≤ 312 : (by linarith)
                        ... = 2 * 468 / 3 : by norm_num
                        ... ≤ 2 * n / 3 : begin
                            apply nat.div_le_div_right ,
                            apply nat.mul_le_mul_left ,
                            assumption ,
                        end
                ),
                have h := primorial_bound (2*n / 3) q,
                have h' := b_lt_c_of_ab_lt_c _ _ _ (by linarith) h,
                rw nat.pow_mul at h' ,
                have h'' : (2 ^ 2 = 4) := by refl ,
                rw h'' at h' ,
                apply nat.mul_le_mul_left ,
                apply le_of_lt , assumption ,
            end
    ... < 4^n : main_bound n a
    ),
    linarith ,

end

/- full bertrand's postulate -/

lemma prime_2 : nat.prime 2 := dec_trivial
lemma prime_3 : nat.prime 3 := dec_trivial
lemma prime_5 : nat.prime 5 := dec_trivial
lemma prime_7 : nat.prime 7 := dec_trivial
lemma prime_13 : nat.prime 13 := dec_trivial
lemma prime_23 : nat.prime 23 := dec_trivial
lemma prime_43 : nat.prime 43 := dec_trivial
lemma prime_83 : nat.prime 83 := dec_trivial
lemma prime_163 : nat.prime 163 := sorry
lemma prime_317 : nat.prime 317 := sorry
lemma prime_631 : nat.prime 631 := sorry

theorem bertrands_postulate : ∀ (n : ℕ) ,
    n ≥ 1 →
    ∃ p , nat.prime p ∧ n < p ∧ p ≤ 2*n :=
begin
    intros ,
    by_cases (n < 2) , existsi 2 ,
        exact (and.intro prime_2 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _2_le_n ,

    by_cases (n < 3) , existsi 3 ,
        exact (and.intro prime_3 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _3_le_n ,

    by_cases (n < 5) , existsi 5 ,
        exact (and.intro prime_5 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _5_le_n ,

    by_cases (n < 7) , existsi 7 ,
        exact (and.intro prime_7 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _7_le_n ,

    by_cases (n < 13) , existsi 13 ,
        exact (and.intro prime_13 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _13_le_n ,

    by_cases (n < 23) , existsi 23 ,
        exact (and.intro prime_23 (and.intro (by assumption) (by linarith))) ,
    simp at h , rename h _23_le_n ,

    by_cases (n < 43) , existsi 43 ,
        have p_le_2n : 43 ≤ 2*n :=
            (calc 43 ≤ 2*23 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_43 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _43_le_n ,

    by_cases (n < 83) , existsi 83 ,
        have p_le_2n : 83 ≤ 2*n :=
            (calc 83 ≤ 2*43 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_83 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _83_le_n ,

    by_cases (n < 163) , existsi 163 ,
        have p_le_2n : 163 ≤ 2*n :=
            (calc 163 ≤ 2*83 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_163 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _163_le_n ,

    by_cases (n < 317) , existsi 317 ,
        have p_le_2n : 317 ≤ 2*n :=
            (calc 317 ≤ 2*163 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_317 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _317_le_n ,

    by_cases (n < 631) , existsi 631 ,
        have p_le_2n : 631 ≤ 2*n :=
            (calc 631 ≤ 2*317 : by norm_num
            ... ≤ 2*n : begin apply nat.mul_le_mul_left , assumption end),
        exact (and.intro prime_631 (and.intro (by assumption) (by assumption))) ,
    simp at h , rename h _631_le_n ,

    have t := (calc 468 ≤ 631 : by norm_num ... ≤ n : by assumption) ,
    exact (bertrands_postulate_main n t) ,
end


import data.nat.basic
import tactic
import data.int.basic
import tactic.nth_rewrite.default

def fib : ℕ → ℕ
| 0     := 0
| 1     := 1
| (n+2) := fib (n+1) + fib n

def luc: ℕ → ℕ
| 0 := 2
| 1 := 1
| (n+2) := luc(n+1) +luc n

@[simp] lemma fib_add ( n :  ℕ) : fib (n+2) = fib (n+1) + fib (n):= by refl
@[simp] lemma luc_add (n: ℕ ): luc (n+2) = luc (n+1) + luc n:= by refl
@[simp] lemma fib_zero : fib 0 =0:= by refl
@[simp] lemma fib_one : fib 1 = 1:= by refl
@[simp] lemma fib_two : fib 2= 1:= by refl
@[simp] lemma luc_zero : luc 0 =2:= by refl
@[simp] lemma luc_one : luc 1 = 1:= by refl

@[simp] lemma fib_luc_rec (n: ℕ ): luc(n+1) = fib(n+2) + fib (n):=
begin
    revert n,
    apply nat.two_step_induction,
    {simp},
    {simp},
    {intros, simp * at *, ring}
    end

lemma nat_case_bash (h n : ℕ ):  h ≤ n ∨ ∃ (k: ℕ ), h=k+n+1:=
begin
    have t1:= le_or_lt h n, cases t1 with t1 t2,
    {left, assumption},
    {right, use h-(n+1), omega }
end

lemma fib_luc1 (m n : ℕ ) : 2 * fib (m+n) = fib (m) * luc (n) + luc(m) * fib(n):=
begin
    revert n, apply nat.two_step_induction,
    {simp [mul_comm]},
    {cases nat_case_bash m 0, interval_cases m,
        {cases h with k h, rw h, simp, ring},
        {simp}},
    {intros, repeat {rw nat.succ_eq_add_one at *}, rw [show m+ (n+1+1) = (m+n)+2, by ring], nth_rewrite 0 ← add_assoc at IH2,
    rw fib_add, rw mul_add, rw IH1, rw IH2, rw [show n+1+1 =n+2, from rfl], rw fib_add, rw luc_add, ring,}
end

lemma fib_luc2 (m n : ℕ ): 2* luc (m+n) = 5* fib (m) * fib (n) +luc(m) * luc(n) :=
begin
    revert n, apply nat.two_step_induction,
    {simp [mul_comm]},
    {cases nat_case_bash m 0, interval_cases m,
        {cases h with k h, rw h, simp, ring},
        {simp}},
    {intros, repeat {rw nat.succ_eq_add_one at *}, rw [show m+ (n+1+1) = (m+n)+2, by ring], nth_rewrite 0 ← add_assoc at IH2,
    rw luc_add, rw mul_add, rw IH1, rw IH2, rw [show n+1+1 =n+2, from rfl], rw fib_add, rw luc_add, ring,}
end

lemma fib_luc_rec_2 (m n: ℕ): luc(m+n+1) = fib(m+1)*luc(n+1) + fib(m)*luc(n):=
begin
    revert n, apply nat.two_step_induction,
    {simp,ring},
    {cases nat_case_bash m 0, interval_cases m,
        {cases h with k h, rw h, simp, ring},
        {simp}},
    {intros, repeat {rw nat.succ_eq_add_one at *}, rw [show m+ (n+1+1) = (m+n)+2, by ring], nth_rewrite 0 ← add_assoc at IH2,
    rw luc_add, rw IH1, rw IH2, ring,}
end

lemma fib_luc3_pre (d: ℕ ):  (↑(luc (d + 2)) * ↑(fib (d + 1)) + 2 * (-1) ^ (d + 1): ℤ) = (↑(luc (d + 1)) * ↑(fib (d + 2)): ℤ):=
begin
    revert d, apply nat.two_step_induction,
    {ring},
    {ring},
    {intros p q r, repeat {rw nat.succ_eq_add_one at *}, clear r,
    rw [luc_add, fib_add], push_cast, rw [add_mul, mul_add],
    have t1: (-1: ℤ )^(p+1+1+1) = (-1: ℤ )^ (p+1), rw pow_add (-1: ℤ ) (p+1) (1+1),rw [show (-1: ℤ )^(1+1)=1, from rfl], rw mul_one,
    rw t1, rw mul_add, rw [show p+1+1=p+2, from rfl], repeat{rw add_assoc},
    rw q,
    nth_rewrite_rhs 0 fib_add, push_cast, rw mul_add,
    nth_rewrite_rhs 0 fib_add, push_cast, rw mul_add,
    nth_rewrite_rhs 0 luc_add, push_cast, rw add_mul,
    ring}
end

lemma fib_luc3 (m: ℕ) : ((luc(2*m) + 2 * (-1: ℤ )^(m)) : ℤ )= ((luc(m))^2 : ℤ ):=
begin
    cases m with d hd,
    {simp, ring},
    {rw nat.succ_eq_add_one, rw [show 2*(d+1)= d+(d+1)+1, by ring], rw fib_luc_rec_2,
    rw fib_luc_rec d, rw pow_two,
    suffices goal:  (↑(luc (d + 2)) * ↑(fib (d + 1)) + 2 * (-1) ^ (d + 1): ℤ) = (↑(luc (d + 1)) * ↑(fib (d + 2)): ℤ),
        {push_cast,
        rw [add_right_comm, mul_comm], rw (show d+1+1=d+2, from rfl), rw goal,
        rw fib_luc_rec, push_cast, ring},
    {apply fib_luc3_pre}}
end

lemma fib_luc1_sub (m n: ℕ ) : (2 * fib (m) :ℤ ) =  (-1: ℤ )^(n)*fib (m+n)  *luc (n) + (-1: ℤ )^(n+1)*luc(m+n) * fib(n):=
begin
    have t1: (-1: ℤ )^(n+1) = -(-1)^n, rw pow_add, ring,
    revert m, apply nat.two_step_induction,
    {rw t1,simp, ring},
    {rw t1, cases nat_case_bash n 0,  interval_cases n,
        {cases h with k h,  rw [show k+0+1 = k+1, from rfl] at h, rw h,
        rw [show 1+(k+1) = k+2, by ring], rw [show ((-1) ^ (k + 1) * ↑(fib (k + 2)) * ↑(luc (k + 1)): ℤ )=  ↑(luc (k + 1))  * ↑(fib (k + 2))*(-1) ^ (k + 1) , by ring], rw ← fib_luc3_pre,
        rw add_mul, rw fib_one, nth_rewrite_rhs 0 mul_comm, rw add_right_comm, nth_rewrite_rhs 1 mul_assoc, ring, rw int.sub_self,
        rw mul_assoc, rw ← pow_add, rw[show k+1 +(k+1) = 2*(k+1), by ring], rw pow_mul, rw [show (-1 : ℤ )^2 =1, from rfl], simp},
        {swap, simp}},
    {intros k IH1 IH2, repeat {rw nat.succ_eq_add_one at *}, rw [show  (k+1+1) = k+2, by refl],
    rw fib_add, push_cast, rw mul_add, rw IH1, rw IH2,  rw [show k+2+n = k+n+2, by ring], rw fib_add, rw luc_add, push_cast, rw mul_add,  rw mul_add, rw add_mul, rw [show k+1+n =k+n+1, by ring], ring}
end

lemma fib_luc_4 (n : ℕ ) : (-1: ℤ )^(n+1)*5*(fib n)^2 + (-1: ℤ )^n * (luc n)^2 =4:=
begin
    suffices goal: (-1) ^ (n + 1) * 5 * ↑(fib n) ^ 2 + (-1) ^ n * ↑(luc n) ^ 2 + (-1:ℤ )^n * ↑(luc n)^2 = 4+ (-1:ℤ )^n * ↑(luc n)^2, simp * at *,
    repeat{nth_rewrite_lhs 0 ← fib_luc3}, rw add_assoc, rw ← mul_add ((-1: ℤ )^n) _ _,
    rw [show ↑(luc (2 * n)) + 2 * (-1) ^ n + (↑(luc (2 * n)) + 2 * (-1: ℤ ) ^ n) = ↑ (2*luc (2 * n)) + 4 * (-1) ^ n, by ring], rw [show 2*n = n+n, by ring], rw fib_luc2,
    have t1: (-1: ℤ )^(n+1) = -(-1)^n, rw pow_add, ring, rw t1,clear t1,
    rw mul_add,
    have t3: (-1: ℤ )^n *(4 *(-1)^n) = 4, rw ← mul_assoc,rw mul_comm, rw ← mul_assoc, rw ← pow_add, rw [show n+n=2*n, by ring], rw pow_mul, simp,
    rw add_comm,repeat{rw add_assoc},  rw t3, push_cast, rw mul_add, ring,
end

lemma fib_luc2_sub (m n : ℕ ) :  (2 * luc (m) :ℤ ) =  (-1: ℤ )^(n+1)*5*fib (m+n)  *fib (n) + (-1: ℤ )^(n)*luc(m+n) * luc(n):=
begin
    have t1: (-1: ℤ )^(n+1) = -(-1)^n, rw pow_add, ring, rw t1,
    have main: (2*(2 * ↑(luc m)):ℤ ) = 2*(-(-1) ^ n * 5 * ↑(fib (m + n)) * ↑(fib n) + (-1) ^ n * ↑(luc (m + n)) * ↑(luc n)),
    { rw mul_add, rw eq_comm,
    have t2: (2 * (-(-1) ^ n * 5 * ↑(fib (m + n)) * ↑(fib n)) :ℤ ) = ↑ (2 *(fib (m + n)))* (-(-1) ^ n * 5 *  ↑(fib n)), push_cast, ring,
    rw t2, rw fib_luc1,
    have t3: (2 * ((-1) ^ n * ↑(luc (m + n)) * ↑(luc n)) : ℤ ) = ↑(2  * (luc (m + n))) *(-1) ^ n* ↑(luc n), push_cast, ring,
    rw t3, rw fib_luc2,
    suffices goal: (-1: ℤ ) ^ n * ↑(luc m) * ↑(luc n) ^ 2 - 5 * (-1: ℤ ) ^ n * ↑(fib n) ^ 2 * ↑(luc m) = 4 * ↑(luc m),
    {push_cast, simp, ring, assumption},
    {have:= fib_luc_4 n, rw t1 at this, rw ← this, ring}
    },
    {rw ←  int.mul_div_cancel_left (2 * ↑(luc m)), rw main, rw int.mul_div_cancel_left, linarith, linarith}
end


import game.limits.bounded_if_convergent -- hide
import game.limits.Lemmas -- hide
import game.limits.Blockus_time -- hide
import game.sets.L01defs -- hide
import game.sup_inf.GLBprop_if_LUBprop -- hide
import data.real.basic -- hide
import tactic.linarith -- hide

namespace xena -- hide

/-
# Chapter 7 : Limits

## Level 10

This lemma will help in dealing with obvious math roadblocks that Lean sometimes
does not comprehend well.

Just looking at this equation and all the hypothesis, we know it's true.
But Lean just needs to know this.
-/

lemma bs_lemma {γ d α β ε : ℝ} (ha : 0 < γ) (hb : 0 < d) (hc : 0 < α)(hd : 0 ≤ β)(he : 0 < ε) (gnz : γ ≠ 0) (anz : α ≠ 0) (dnz : d ≠ 0) (h : d ≤ γ) (h' : (1 / (d * α) * β ≤ ε))
: (1 / (γ * α) * β ≤ ε) :=
begin
  have L1 : d * α ≤ γ * α, exact mul_le_mul_right_plz1 hc h,
  have L2 : 1 / (γ * α) ≤ 1 / (d * α), exact stuff4 gnz dnz anz h,
  have L2 : (1 / (γ * α)) * β ≤ (1 / (d * α)) * β, exact mul_le_mul_right1 hd L2,
  linarith,

end

end xena -- hide


import data.zmod.algebra
import tactic

class ec_field (F : Type*) extends field F :=
(deceqF : decidable_eq F)
(two_ne_zero : (2 : F) ≠ 0)

instance ec_field.decidable_eq (F : Type*) [h : ec_field F] : decidable_eq F :=
h.deceqF

def on_ec {F : Type*} [field F] (p : F × F) := p.2^2 = p.1^3 + 7

lemma eq_or_eq_neg_of_eq_of_on_ec {F : Type*} [field F] {x1 y1 x2 y2 : F}
    (hp : on_ec (x1, y1)) (hq : on_ec (x2, y2)) (heq : x1 = x2 ) :
  y1 = y2 ∨ y1 = -y2 :=
begin
  have : y1^2 = y2^2,
  { apply hp.trans, symmetry, apply hq.trans, dsimp, rw heq },
  apply eq_or_eq_neg_of_sq_eq_sq _ _ this
end

lemma eq_of_on_ec {F : Type*} [field F] {x1 y1 x2 y2 : F}
    (hp : on_ec (x1, y1)) (hq : on_ec (x2, y2)) (heq : x1 = x2 ) (hne : y1 ≠ -y2) :
  y1 = y2 :=
or.resolve_right (eq_or_eq_neg_of_eq_of_on_ec hp hq heq) hne

@[ext] structure AffineECPoint (F : Type*) [field F] :=
(x y : F) (h : on_ec (x, y))

inductive ECPoint  (F : Type*) [field F]
| ZeroPoint : ECPoint
| AffinePoint : AffineECPoint F → ECPoint

variables {F : Type*} [ec_field F]

def ec_double_slope (p : F × F) := 3 * p.1^2 / (2 * p.2)

def ec_double (p : F × F) :=
  let slope := ec_double_slope p,
      new_x := slope^2 - 2 * p.1 in
  (new_x, (p.1 - new_x) * slope - p.2)

theorem on_ec_ec_double {p : F × F} (h : on_ec p) (h' : p.2 ≠ 0) :
  on_ec (ec_double p) :=
begin
  cases p with x y,
  have pow_three : ∀ z : F, z^3 = z * z * z, { intro z, simp [pow_succ, mul_assoc] },
  simp [on_ec, ec_double, ec_double_slope] at *,
  apply eq_of_sub_eq_zero,
  field_simp [ec_field.two_ne_zero, h, h', pow_two, pow_three], ring_nf,
  simp [h], ring_nf,
end

def ec_add_slope (p1 p2 : F × F) := (p2.2 - p1.2) / (p2.1 - p1.1)

def ec_add (p1 p2 : F × F) : F × F :=
  let slope := ec_add_slope p1 p2,
      new_x := slope^2 - (p1.1 + p2.1) in
  (new_x, (p1.1 - new_x) * slope - p1.2)

theorem on_ec_ec_add {p1 p2 : F × F} (h1 : on_ec p1) (h2 : on_ec p2) (h' : p1.1 ≠ p2.1) :
  on_ec (ec_add p1 p2) :=
begin
  cases p1 with x1 y1,
  cases p2 with x2 y2,
  have pow_three : ∀ z : F, z^3 = z * z^2, { intro z, simp [pow_succ, mul_assoc], },
  have pow_four : ∀ z : F, z^4 = z^2 * z^2, { intro z, simp [pow_succ, mul_assoc], },
  simp [on_ec, ec_add, ec_add_slope] at *,
  have : x2 - x1 ≠ 0, { contrapose! h', symmetry, apply eq_of_sub_eq_zero h' },
  apply eq_of_sub_eq_zero,
  field_simp [ec_field.two_ne_zero, pow_two, pow_three, this], ring_nf,
  simp [h1, h2, pow_three, pow_four], ring_nf,
  simp [h1, h2], ring_nf
end

namespace ECPoint

protected def add : ECPoint F → ECPoint F → ECPoint F
| ZeroPoint       b               := b
| a               ZeroPoint       := a
| (AffinePoint a) (AffinePoint b) :=
    if axbx: a.x = b.x then
      if ayby: a.y = -b.y then
        -- a = -b
        ZeroPoint
      else
        have a.y = b.y, from eq_of_on_ec a.h b.h axbx ayby,
        have a.y ≠ 0, by { contrapose! ayby, rw [←this, ayby, neg_zero] },
        let p := ec_double (a.x, a.y) in
        AffinePoint ⟨p.1, p.2, on_ec_ec_double a.h this⟩
    else
      let p := ec_add (a.x, a.y) (b.x, b.y) in
      AffinePoint ⟨p.1, p.2, on_ec_ec_add a.h b.h axbx⟩

protected def neg : ECPoint F → ECPoint F
| ZeroPoint       := ZeroPoint
| (AffinePoint a) := AffinePoint ⟨a.x, -a.y, by { simp [on_ec], exact a.h} ⟩

protected theorem add_left_neg (a : ECPoint F) : a.neg.add a = ZeroPoint :=
by { cases a; simp [ECPoint.neg, ECPoint.add] }

protected theorem add_comm (a b: ECPoint F) : a.add b = b.add a :=
begin
  cases a; cases b; simp [ECPoint.add],
  by_cases h : a.x = b.x,
  { rw [dif_pos h, dif_pos h.symm], simp [h],
    by_cases h': a.y = -b.y,
    { rw [dif_pos h', dif_pos (eq_neg_of_eq_neg h')] },
    rw [dif_neg h', dif_neg (λ h'', h' (eq_neg_of_eq_neg h''))],
    simp [eq_of_on_ec a.h b.h h h'] },
  rw [dif_neg h, dif_neg (ne.symm h)],
  have : ec_add_slope (a.x, a.y) (b.x, b.y) = ec_add_slope (b.x, b.y) (a.x, a.y),
  { simp [ec_add_slope], rw [←neg_sub b.y a.y, ←neg_sub b.x a.x, neg_div_neg_eq] },
  congr' 1, ext; dsimp [ec_add]; simp [add_comm],
  { simp [this] },
  have h1 : a.x - b.x ≠ 0, { rwa [ne, sub_eq_zero] },
  have h2 : b.x - a.x ≠ 0, { rw [ne, sub_eq_zero], exact ne.symm h },
  field_simp [ec_add_slope, h1, h2, pow_two],
  ring_nf
end

end ECPoint

instance : add_comm_group (ECPoint F) :=
{ add          := ECPoint.add,
  neg          := ECPoint.neg,
  zero         := ECPoint.ZeroPoint,
  add_assoc    := sorry,
  zero_add     := by { intro a, cases a; simp [ECPoint.add] },
  add_zero     := by { intro a, cases a; simp [ECPoint.add] },
  add_left_neg := ECPoint.add_left_neg,
  add_comm     := ECPoint.add_comm }

theorem ECPoint.add_def (x y : ECPoint F) : x + y = ECPoint.add x y := rfl

theorem ECPoint.neg_def (x : ECPoint F) : - x = ECPoint.neg x := rfl

theorem ECPoint.zero_def : (0 : ECPoint F) = ECPoint.ZeroPoint := rfl


/-
Copyright (c) 2019 Zhouhang Zhou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Zhouhang Zhou
-/
import measure_theory.function.lp_order

/-!
# Integrable functions and `L¹` space

In the first part of this file, the predicate `integrable` is defined and basic properties of
integrable functions are proved.

Such a predicate is already available under the name `mem_ℒp 1`. We give a direct definition which
is easier to use, and show that it is equivalent to `mem_ℒp 1`

In the second part, we establish an API between `integrable` and the space `L¹` of equivalence
classes of integrable functions, already defined as a special case of `L^p` spaces for `p = 1`.

## Notation

* `α →₁[μ] β` is the type of `L¹` space, where `α` is a `measure_space` and `β` is a `normed_group`
  with a `second_countable_topology`. `f : α →ₘ β` is a "function" in `L¹`. In comments, `[f]` is
  also used to denote an `L¹` function.

  `₁` can be typed as `\1`.

## Main definitions

* Let `f : α → β` be a function, where `α` is a `measure_space` and `β` a `normed_group`.
  Then `has_finite_integral f` means `(∫⁻ a, ∥f a∥₊) < ∞`.

* If `β` is moreover a `measurable_space` then `f` is called `integrable` if
  `f` is `measurable` and `has_finite_integral f` holds.

## Implementation notes

To prove something for an arbitrary integrable function, a useful theorem is
`integrable.induction` in the file `set_integral`.

## Tags

integrable, function space, l1

-/

noncomputable theory

open_locale classical topological_space big_operators ennreal measure_theory nnreal

open set filter topological_space ennreal emetric measure_theory

variables {α β γ δ : Type*} {m : measurable_space α} {μ ν : measure α} [measurable_space δ]
variables [normed_group β]
variables [normed_group γ]

namespace measure_theory

/-! ### Some results about the Lebesgue integral involving a normed group -/

lemma lintegral_nnnorm_eq_lintegral_edist (f : α → β) :
  ∫⁻ a, ∥f a∥₊ ∂μ = ∫⁻ a, edist (f a) 0 ∂μ :=
by simp only [edist_eq_coe_nnnorm]

lemma lintegral_norm_eq_lintegral_edist (f : α → β) :
  ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ = ∫⁻ a, edist (f a) 0 ∂μ :=
by simp only [of_real_norm_eq_coe_nnnorm, edist_eq_coe_nnnorm]

lemma lintegral_edist_triangle {f g h : α → β}
  (hf : ae_strongly_measurable f μ) (hh : ae_strongly_measurable h μ) :
  ∫⁻ a, edist (f a) (g a) ∂μ ≤ ∫⁻ a, edist (f a) (h a) ∂μ + ∫⁻ a, edist (g a) (h a) ∂μ :=
begin
  rw ← lintegral_add_left' (hf.edist hh),
  refine lintegral_mono (λ a, _),
  apply edist_triangle_right
end

lemma lintegral_nnnorm_zero : ∫⁻ a : α, ∥(0 : β)∥₊ ∂μ = 0 := by simp

lemma lintegral_nnnorm_add_left
  {f : α → β} (hf : ae_strongly_measurable f μ) (g : α → γ) :
  ∫⁻ a, ∥f a∥₊ + ∥g a∥₊ ∂μ = ∫⁻ a, ∥f a∥₊ ∂μ + ∫⁻ a, ∥g a∥₊ ∂μ :=
lintegral_add_left' hf.ennnorm _

lemma lintegral_nnnorm_add_right
  (f : α → β) {g : α → γ} (hg : ae_strongly_measurable g μ) :
  ∫⁻ a, ∥f a∥₊ + ∥g a∥₊ ∂μ = ∫⁻ a, ∥f a∥₊ ∂μ + ∫⁻ a, ∥g a∥₊ ∂μ :=
lintegral_add_right' _ hg.ennnorm

lemma lintegral_nnnorm_neg {f : α → β} :
  ∫⁻ a, ∥(-f) a∥₊ ∂μ = ∫⁻ a, ∥f a∥₊ ∂μ :=
by simp only [pi.neg_apply, nnnorm_neg]

/-! ### The predicate `has_finite_integral` -/

/-- `has_finite_integral f μ` means that the integral `∫⁻ a, ∥f a∥ ∂μ` is finite.
  `has_finite_integral f` means `has_finite_integral f volume`. -/
def has_finite_integral {m : measurable_space α} (f : α → β) (μ : measure α . volume_tac) : Prop :=
∫⁻ a, ∥f a∥₊ ∂μ < ∞

lemma has_finite_integral_iff_norm (f : α → β) :
  has_finite_integral f μ ↔ ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ < ∞ :=
by simp only [has_finite_integral, of_real_norm_eq_coe_nnnorm]

lemma has_finite_integral_iff_edist (f : α → β) :
  has_finite_integral f μ ↔ ∫⁻ a, edist (f a) 0 ∂μ < ∞ :=
by simp only [has_finite_integral_iff_norm, edist_dist, dist_zero_right]

lemma has_finite_integral_iff_of_real {f : α → ℝ} (h : 0 ≤ᵐ[μ] f) :
  has_finite_integral f μ ↔ ∫⁻ a, ennreal.of_real (f a) ∂μ < ∞ :=
have lintegral_eq : ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ = ∫⁻ a, ennreal.of_real (f a) ∂μ :=
begin
  refine lintegral_congr_ae (h.mono $ λ a h, _),
  rwa [real.norm_eq_abs, abs_of_nonneg]
end,
by rw [has_finite_integral_iff_norm, lintegral_eq]

lemma has_finite_integral_iff_of_nnreal {f : α → ℝ≥0} :
  has_finite_integral (λ x, (f x : ℝ)) μ ↔ ∫⁻ a, f a ∂μ < ∞ :=
by simp [has_finite_integral_iff_norm]

lemma has_finite_integral.mono {f : α → β} {g : α → γ} (hg : has_finite_integral g μ)
  (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ ∥g a∥) : has_finite_integral f μ :=
begin
  simp only [has_finite_integral_iff_norm] at *,
  calc ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ ≤ ∫⁻ (a : α), (ennreal.of_real ∥g a∥) ∂μ :
    lintegral_mono_ae (h.mono $ assume a h, of_real_le_of_real h)
    ... < ∞ : hg
end

lemma has_finite_integral.mono' {f : α → β} {g : α → ℝ} (hg : has_finite_integral g μ)
  (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ g a) : has_finite_integral f μ :=
hg.mono $ h.mono $ λ x hx, le_trans hx (le_abs_self _)

lemma has_finite_integral.congr' {f : α → β} {g : α → γ} (hf : has_finite_integral f μ)
  (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  has_finite_integral g μ :=
hf.mono $ eventually_eq.le $ eventually_eq.symm h

lemma has_finite_integral_congr' {f : α → β} {g : α → γ} (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  has_finite_integral f μ ↔ has_finite_integral g μ :=
⟨λ hf, hf.congr' h, λ hg, hg.congr' $ eventually_eq.symm h⟩

lemma has_finite_integral.congr {f g : α → β} (hf : has_finite_integral f μ) (h : f =ᵐ[μ] g) :
  has_finite_integral g μ :=
hf.congr' $ h.fun_comp norm

lemma has_finite_integral_congr {f g : α → β} (h : f =ᵐ[μ] g) :
  has_finite_integral f μ ↔ has_finite_integral g μ :=
has_finite_integral_congr' $ h.fun_comp norm

lemma has_finite_integral_const_iff {c : β} :
  has_finite_integral (λ x : α, c) μ ↔ c = 0 ∨ μ univ < ∞ :=
by simp [has_finite_integral, lintegral_const, lt_top_iff_ne_top, or_iff_not_imp_left]

lemma has_finite_integral_const [is_finite_measure μ] (c : β) :
  has_finite_integral (λ x : α, c) μ :=
has_finite_integral_const_iff.2 (or.inr $ measure_lt_top _ _)

lemma has_finite_integral_of_bounded [is_finite_measure μ] {f : α → β} {C : ℝ}
  (hC : ∀ᵐ a ∂μ, ∥f a∥ ≤ C) : has_finite_integral f μ :=
(has_finite_integral_const C).mono' hC

lemma has_finite_integral.mono_measure {f : α → β} (h : has_finite_integral f ν) (hμ : μ ≤ ν) :
  has_finite_integral f μ :=
lt_of_le_of_lt (lintegral_mono' hμ le_rfl) h

lemma has_finite_integral.add_measure {f : α → β} (hμ : has_finite_integral f μ)
  (hν : has_finite_integral f ν) : has_finite_integral f (μ + ν) :=
begin
  simp only [has_finite_integral, lintegral_add_measure] at *,
  exact add_lt_top.2 ⟨hμ, hν⟩
end

lemma has_finite_integral.left_of_add_measure {f : α → β} (h : has_finite_integral f (μ + ν)) :
  has_finite_integral f μ :=
h.mono_measure $ measure.le_add_right $ le_rfl

lemma has_finite_integral.right_of_add_measure {f : α → β} (h : has_finite_integral f (μ + ν)) :
  has_finite_integral f ν :=
h.mono_measure $ measure.le_add_left $ le_rfl

@[simp] lemma has_finite_integral_add_measure {f : α → β} :
  has_finite_integral f (μ + ν) ↔ has_finite_integral f μ ∧ has_finite_integral f ν :=
⟨λ h, ⟨h.left_of_add_measure, h.right_of_add_measure⟩, λ h, h.1.add_measure h.2⟩

lemma has_finite_integral.smul_measure {f : α → β} (h : has_finite_integral f μ) {c : ℝ≥0∞}
  (hc : c ≠ ∞) : has_finite_integral f (c • μ) :=
begin
  simp only [has_finite_integral, lintegral_smul_measure] at *,
  exact mul_lt_top hc h.ne
end

@[simp] lemma has_finite_integral_zero_measure {m : measurable_space α} (f : α → β) :
  has_finite_integral f (0 : measure α) :=
by simp only [has_finite_integral, lintegral_zero_measure, with_top.zero_lt_top]

variables (α β μ)
@[simp] lemma has_finite_integral_zero : has_finite_integral (λa:α, (0:β)) μ :=
by simp [has_finite_integral]
variables {α β μ}

lemma has_finite_integral.neg {f : α → β} (hfi : has_finite_integral f μ) :
  has_finite_integral (-f) μ :=
by simpa [has_finite_integral] using hfi

@[simp] lemma has_finite_integral_neg_iff {f : α → β} :
  has_finite_integral (-f) μ ↔ has_finite_integral f μ :=
⟨λ h, neg_neg f ▸ h.neg, has_finite_integral.neg⟩

lemma has_finite_integral.norm {f : α → β} (hfi : has_finite_integral f μ) :
  has_finite_integral (λa, ∥f a∥) μ :=
have eq : (λa, (nnnorm ∥f a∥ : ℝ≥0∞)) = λa, (∥f a∥₊ : ℝ≥0∞),
  by { funext, rw nnnorm_norm },
by { rwa [has_finite_integral, eq] }

lemma has_finite_integral_norm_iff (f : α → β) :
  has_finite_integral (λa, ∥f a∥) μ ↔ has_finite_integral f μ :=
has_finite_integral_congr' $ eventually_of_forall $ λ x, norm_norm (f x)

lemma has_finite_integral_to_real_of_lintegral_ne_top
  {f : α → ℝ≥0∞} (hf : ∫⁻ x, f x ∂μ ≠ ∞) :
  has_finite_integral (λ x, (f x).to_real) μ :=
begin
  have : ∀ x, (∥(f x).to_real∥₊ : ℝ≥0∞) =
    @coe ℝ≥0 ℝ≥0∞ _ (⟨(f x).to_real, ennreal.to_real_nonneg⟩ : ℝ≥0),
  { intro x, rw real.nnnorm_of_nonneg },
  simp_rw [has_finite_integral, this],
  refine lt_of_le_of_lt (lintegral_mono (λ x, _)) (lt_top_iff_ne_top.2 hf),
  by_cases hfx : f x = ∞,
  { simp [hfx] },
  { lift f x to ℝ≥0 using hfx with fx,
    simp [← h] }
end

lemma is_finite_measure_with_density_of_real {f : α → ℝ} (hfi : has_finite_integral f μ) :
  is_finite_measure (μ.with_density (λ x, ennreal.of_real $ f x)) :=
begin
  refine is_finite_measure_with_density ((lintegral_mono $ λ x, _).trans_lt hfi).ne,
  exact real.of_real_le_ennnorm (f x)
end

section dominated_convergence

variables {F : ℕ → α → β} {f : α → β} {bound : α → ℝ}

lemma all_ae_of_real_F_le_bound (h : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a) :
  ∀ n, ∀ᵐ a ∂μ, ennreal.of_real ∥F n a∥ ≤ ennreal.of_real (bound a) :=
λn, (h n).mono $ λ a h, ennreal.of_real_le_of_real h

lemma all_ae_tendsto_of_real_norm (h : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top $ 𝓝 $ f a) :
  ∀ᵐ a ∂μ, tendsto (λn, ennreal.of_real ∥F n a∥) at_top $ 𝓝 $ ennreal.of_real ∥f a∥ :=
h.mono $
  λ a h, tendsto_of_real $ tendsto.comp (continuous.tendsto continuous_norm _) h

lemma all_ae_of_real_f_le_bound (h_bound : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a)
  (h_lim : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top (𝓝 (f a))) :
  ∀ᵐ a ∂μ, ennreal.of_real ∥f a∥ ≤ ennreal.of_real (bound a) :=
begin
  have F_le_bound := all_ae_of_real_F_le_bound h_bound,
  rw ← ae_all_iff at F_le_bound,
  apply F_le_bound.mp ((all_ae_tendsto_of_real_norm h_lim).mono _),
  assume a tendsto_norm F_le_bound,
  exact le_of_tendsto' tendsto_norm (F_le_bound)
end

lemma has_finite_integral_of_dominated_convergence {F : ℕ → α → β} {f : α → β} {bound : α → ℝ}
  (bound_has_finite_integral : has_finite_integral bound μ)
  (h_bound : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a)
  (h_lim : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top (𝓝 (f a))) :
  has_finite_integral f μ :=
/- `∥F n a∥ ≤ bound a` and `∥F n a∥ --> ∥f a∥` implies `∥f a∥ ≤ bound a`,
  and so `∫ ∥f∥ ≤ ∫ bound < ∞` since `bound` is has_finite_integral -/
begin
  rw has_finite_integral_iff_norm,
  calc ∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ ≤ ∫⁻ a, ennreal.of_real (bound a) ∂μ :
    lintegral_mono_ae $ all_ae_of_real_f_le_bound h_bound h_lim
    ... < ∞ :
    begin
      rw ← has_finite_integral_iff_of_real,
      { exact bound_has_finite_integral },
      exact (h_bound 0).mono (λ a h, le_trans (norm_nonneg _) h)
    end
end

lemma tendsto_lintegral_norm_of_dominated_convergence
  {F : ℕ → α → β} {f : α → β} {bound : α → ℝ}
  (F_measurable : ∀ n, ae_strongly_measurable (F n) μ)
  (bound_has_finite_integral : has_finite_integral bound μ)
  (h_bound : ∀ n, ∀ᵐ a ∂μ, ∥F n a∥ ≤ bound a)
  (h_lim : ∀ᵐ a ∂μ, tendsto (λ n, F n a) at_top (𝓝 (f a))) :
  tendsto (λn, ∫⁻ a, (ennreal.of_real ∥F n a - f a∥) ∂μ) at_top (𝓝 0) :=
have f_measurable : ae_strongly_measurable f μ :=
  ae_strongly_measurable_of_tendsto_ae _ F_measurable h_lim,
let b := λ a, 2 * ennreal.of_real (bound a) in
/- `∥F n a∥ ≤ bound a` and `F n a --> f a` implies `∥f a∥ ≤ bound a`, and thus by the
  triangle inequality, have `∥F n a - f a∥ ≤ 2 * (bound a). -/
have hb : ∀ n, ∀ᵐ a ∂μ, ennreal.of_real ∥F n a - f a∥ ≤ b a,
begin
  assume n,
  filter_upwards [all_ae_of_real_F_le_bound h_bound n, all_ae_of_real_f_le_bound h_bound h_lim]
    with a h₁ h₂,
  calc ennreal.of_real ∥F n a - f a∥ ≤ (ennreal.of_real ∥F n a∥) + (ennreal.of_real ∥f a∥) :
  begin
    rw [← ennreal.of_real_add],
    apply of_real_le_of_real,
    { apply norm_sub_le }, { exact norm_nonneg _ }, { exact norm_nonneg _ }
  end
    ... ≤ (ennreal.of_real (bound a)) + (ennreal.of_real (bound a)) : add_le_add h₁ h₂
    ... = b a : by rw ← two_mul
end,
/- On the other hand, `F n a --> f a` implies that `∥F n a - f a∥ --> 0`  -/
have h : ∀ᵐ a ∂μ, tendsto (λ n, ennreal.of_real ∥F n a - f a∥) at_top (𝓝 0),
begin
  rw ← ennreal.of_real_zero,
  refine h_lim.mono (λ a h, (continuous_of_real.tendsto _).comp _),
  rwa ← tendsto_iff_norm_tendsto_zero
end,
/- Therefore, by the dominated convergence theorem for nonnegative integration, have
  ` ∫ ∥f a - F n a∥ --> 0 ` -/
begin
  suffices h : tendsto (λn, ∫⁻ a, (ennreal.of_real ∥F n a - f a∥) ∂μ) at_top (𝓝 (∫⁻ (a:α), 0 ∂μ)),
  { rwa lintegral_zero at h },
  -- Using the dominated convergence theorem.
  refine tendsto_lintegral_of_dominated_convergence' _ _ hb _ _,
  -- Show `λa, ∥f a - F n a∥` is almost everywhere measurable for all `n`
  { exact λ n,  measurable_of_real.comp_ae_measurable
      ((F_measurable n).sub f_measurable).norm.ae_measurable },
  -- Show `2 * bound` is has_finite_integral
  { rw has_finite_integral_iff_of_real at bound_has_finite_integral,
    { calc ∫⁻ a, b a ∂μ = 2 * ∫⁻ a, ennreal.of_real (bound a) ∂μ :
        by { rw lintegral_const_mul', exact coe_ne_top }
        ... ≠ ∞ : mul_ne_top coe_ne_top bound_has_finite_integral.ne },
    filter_upwards [h_bound 0] with _ h using le_trans (norm_nonneg _) h },
  -- Show `∥f a - F n a∥ --> 0`
  { exact h }
end

end dominated_convergence

section pos_part
/-! Lemmas used for defining the positive part of a `L¹` function -/

lemma has_finite_integral.max_zero {f : α → ℝ} (hf : has_finite_integral f μ) :
  has_finite_integral (λa, max (f a) 0) μ :=
hf.mono $ eventually_of_forall $ λ x, by simp [real.norm_eq_abs, abs_le, abs_nonneg, le_abs_self]

lemma has_finite_integral.min_zero {f : α → ℝ} (hf : has_finite_integral f μ) :
  has_finite_integral (λa, min (f a) 0) μ :=
hf.mono $ eventually_of_forall $ λ x,
  by simp [real.norm_eq_abs, abs_le, abs_nonneg, neg_le, neg_le_abs_self, abs_eq_max_neg, le_total]

end pos_part

section normed_space
variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma has_finite_integral.smul (c : 𝕜) {f : α → β} : has_finite_integral f μ →
  has_finite_integral (c • f) μ :=
begin
  simp only [has_finite_integral], assume hfi,
  calc
    ∫⁻ (a : α), ∥c • f a∥₊ ∂μ = ∫⁻ (a : α), (∥c∥₊) * ∥f a∥₊ ∂μ :
      by simp only [nnnorm_smul, ennreal.coe_mul]
    ... < ∞ :
    begin
      rw lintegral_const_mul',
      exacts [mul_lt_top coe_ne_top hfi.ne, coe_ne_top]
    end
end

lemma has_finite_integral_smul_iff {c : 𝕜} (hc : c ≠ 0) (f : α → β) :
  has_finite_integral (c • f) μ ↔ has_finite_integral f μ :=
begin
  split,
  { assume h,
    simpa only [smul_smul, inv_mul_cancel hc, one_smul] using h.smul c⁻¹ },
  exact has_finite_integral.smul _
end

lemma has_finite_integral.const_mul {f : α → ℝ} (h : has_finite_integral f μ) (c : ℝ) :
  has_finite_integral (λ x, c * f x) μ :=
(has_finite_integral.smul c h : _)

lemma has_finite_integral.mul_const {f : α → ℝ} (h : has_finite_integral f μ) (c : ℝ) :
  has_finite_integral (λ x, f x * c) μ :=
by simp_rw [mul_comm, h.const_mul _]

end normed_space

/-! ### The predicate `integrable` -/

-- variables [measurable_space β] [measurable_space γ] [measurable_space δ]

/-- `integrable f μ` means that `f` is measurable and that the integral `∫⁻ a, ∥f a∥ ∂μ` is finite.
  `integrable f` means `integrable f volume`. -/
def integrable {α} {m : measurable_space α} (f : α → β) (μ : measure α . volume_tac) : Prop :=
ae_strongly_measurable f μ ∧ has_finite_integral f μ

lemma mem_ℒp_one_iff_integrable {f : α → β} : mem_ℒp f 1 μ ↔ integrable f μ :=
by simp_rw [integrable, has_finite_integral, mem_ℒp, snorm_one_eq_lintegral_nnnorm]

lemma integrable.ae_strongly_measurable {f : α → β} (hf : integrable f μ) :
  ae_strongly_measurable f μ :=
hf.1

lemma integrable.ae_measurable [measurable_space β] [borel_space β]
  {f : α → β} (hf : integrable f μ) :
  ae_measurable f μ :=
hf.ae_strongly_measurable.ae_measurable

lemma integrable.has_finite_integral {f : α → β} (hf : integrable f μ) : has_finite_integral f μ :=
hf.2

lemma integrable.mono {f : α → β} {g : α → γ}
  (hg : integrable g μ) (hf : ae_strongly_measurable f μ) (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ ∥g a∥) :
  integrable f μ :=
⟨hf, hg.has_finite_integral.mono h⟩

lemma integrable.mono' {f : α → β} {g : α → ℝ}
  (hg : integrable g μ) (hf : ae_strongly_measurable f μ) (h : ∀ᵐ a ∂μ, ∥f a∥ ≤ g a) :
  integrable f μ :=
⟨hf, hg.has_finite_integral.mono' h⟩

lemma integrable.congr' {f : α → β} {g : α → γ}
  (hf : integrable f μ) (hg : ae_strongly_measurable g μ) (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  integrable g μ :=
⟨hg, hf.has_finite_integral.congr' h⟩

lemma integrable_congr' {f : α → β} {g : α → γ}
  (hf : ae_strongly_measurable f μ) (hg : ae_strongly_measurable g μ) (h : ∀ᵐ a ∂μ, ∥f a∥ = ∥g a∥) :
  integrable f μ ↔ integrable g μ :=
⟨λ h2f, h2f.congr' hg h, λ h2g, h2g.congr' hf $ eventually_eq.symm h⟩

lemma integrable.congr {f g : α → β} (hf : integrable f μ) (h : f =ᵐ[μ] g) :
  integrable g μ :=
⟨hf.1.congr h, hf.2.congr h⟩

lemma integrable_congr {f g : α → β} (h : f =ᵐ[μ] g) :
  integrable f μ ↔ integrable g μ :=
⟨λ hf, hf.congr h, λ hg, hg.congr h.symm⟩

lemma integrable_const_iff {c : β} : integrable (λ x : α, c) μ ↔ c = 0 ∨ μ univ < ∞ :=
begin
  have : ae_strongly_measurable (λ (x : α), c) μ := ae_strongly_measurable_const,
  rw [integrable, and_iff_right this, has_finite_integral_const_iff]
end

lemma integrable_const [is_finite_measure μ] (c : β) : integrable (λ x : α, c) μ :=
integrable_const_iff.2 $ or.inr $ measure_lt_top _ _

lemma mem_ℒp.integrable_norm_rpow {f : α → β} {p : ℝ≥0∞}
  (hf : mem_ℒp f p μ) (hp_ne_zero : p ≠ 0) (hp_ne_top : p ≠ ∞) :
  integrable (λ (x : α), ∥f x∥ ^ p.to_real) μ :=
begin
  rw ← mem_ℒp_one_iff_integrable,
  exact hf.norm_rpow hp_ne_zero hp_ne_top,
end

lemma mem_ℒp.integrable_norm_rpow' [is_finite_measure μ] {f : α → β} {p : ℝ≥0∞}
  (hf : mem_ℒp f p μ) :
  integrable (λ (x : α), ∥f x∥ ^ p.to_real) μ :=
begin
  by_cases h_zero : p = 0,
  { simp [h_zero, integrable_const] },
  by_cases h_top : p = ∞,
  { simp [h_top, integrable_const] },
  exact hf.integrable_norm_rpow h_zero h_top
end

lemma integrable.mono_measure {f : α → β} (h : integrable f ν) (hμ : μ ≤ ν) : integrable f μ :=
⟨h.ae_strongly_measurable.mono_measure hμ, h.has_finite_integral.mono_measure hμ⟩

lemma integrable.of_measure_le_smul {μ' : measure α} (c : ℝ≥0∞) (hc : c ≠ ∞)
  (hμ'_le : μ' ≤ c • μ) {f : α → β} (hf : integrable f μ) :
  integrable f μ' :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.of_measure_le_smul c hc hμ'_le, }

lemma integrable.add_measure {f : α → β} (hμ : integrable f μ) (hν : integrable f ν) :
  integrable f (μ + ν) :=
begin
  simp_rw ← mem_ℒp_one_iff_integrable at hμ hν ⊢,
  refine ⟨hμ.ae_strongly_measurable.add_measure hν.ae_strongly_measurable, _⟩,
  rw [snorm_one_add_measure, ennreal.add_lt_top],
  exact ⟨hμ.snorm_lt_top, hν.snorm_lt_top⟩,
end

lemma integrable.left_of_add_measure {f : α → β} (h : integrable f (μ + ν)) : integrable f μ :=
by { rw ← mem_ℒp_one_iff_integrable at h ⊢, exact h.left_of_add_measure, }

lemma integrable.right_of_add_measure {f : α → β} (h : integrable f (μ + ν)) : integrable f ν :=
by { rw ← mem_ℒp_one_iff_integrable at h ⊢, exact h.right_of_add_measure, }

@[simp] lemma integrable_add_measure {f : α → β} :
  integrable f (μ + ν) ↔ integrable f μ ∧ integrable f ν :=
⟨λ h, ⟨h.left_of_add_measure, h.right_of_add_measure⟩, λ h, h.1.add_measure h.2⟩

@[simp] lemma integrable_zero_measure {m : measurable_space α} {f : α → β} :
  integrable f (0 : measure α) :=
⟨ae_measurable_zero_measure f, has_finite_integral_zero_measure f⟩

theorem integrable_finset_sum_measure {ι} {m : measurable_space α} {f : α → β}
  {μ : ι → measure α} {s : finset ι} :
  integrable f (∑ i in s, μ i) ↔ ∀ i ∈ s, integrable f (μ i) :=
by induction s using finset.induction_on; simp [*]

lemma integrable.smul_measure {f : α → β} (h : integrable f μ) {c : ℝ≥0∞} (hc : c ≠ ∞) :
  integrable f (c • μ) :=
by { rw ← mem_ℒp_one_iff_integrable at h ⊢, exact h.smul_measure hc, }

lemma integrable_smul_measure {f : α → β} {c : ℝ≥0∞} (h₁ : c ≠ 0) (h₂ : c ≠ ∞) :
  integrable f (c • μ) ↔ integrable f μ :=
⟨λ h, by simpa only [smul_smul, ennreal.inv_mul_cancel h₁ h₂, one_smul]
  using h.smul_measure (ennreal.inv_ne_top.2 h₁), λ h, h.smul_measure h₂⟩

lemma integrable.to_average {f : α → β} (h : integrable f μ) :
  integrable f ((μ univ)⁻¹ • μ) :=
begin
  rcases eq_or_ne μ 0 with rfl|hne,
  { rwa smul_zero },
  { apply h.smul_measure, simpa }
end

lemma integrable_average [is_finite_measure μ] {f : α → β} :
  integrable f ((μ univ)⁻¹ • μ) ↔ integrable f μ :=
(eq_or_ne μ 0).by_cases (λ h, by simp [h]) $ λ h,
  integrable_smul_measure (ennreal.inv_ne_zero.2 $ measure_ne_top _ _)
    (ennreal.inv_ne_top.2 $ mt measure.measure_univ_eq_zero.1 h)

lemma integrable_map_measure {f : α → δ} {g : δ → β}
  (hg : ae_strongly_measurable g (measure.map f μ)) (hf : ae_measurable f μ) :
  integrable g (measure.map f μ) ↔ integrable (g ∘ f) μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact mem_ℒp_map_measure_iff hg hf, }

lemma integrable.comp_ae_measurable {f : α → δ} {g : δ → β}
  (hg : integrable g (measure.map f μ)) (hf : ae_measurable f μ) : integrable (g ∘ f) μ :=
(integrable_map_measure hg.ae_strongly_measurable hf).mp hg

lemma integrable.comp_measurable {f : α → δ} {g : δ → β}
  (hg : integrable g (measure.map f μ)) (hf : measurable f) : integrable (g ∘ f) μ :=
hg.comp_ae_measurable hf.ae_measurable

lemma _root_.measurable_embedding.integrable_map_iff
  {f : α → δ} (hf : measurable_embedding f) {g : δ → β} :
  integrable g (measure.map f μ) ↔ integrable (g ∘ f) μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact hf.mem_ℒp_map_measure_iff, }

lemma integrable_map_equiv (f : α ≃ᵐ δ) (g : δ → β) :
  integrable g (measure.map f μ) ↔ integrable (g ∘ f) μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact f.mem_ℒp_map_measure_iff, }

lemma measure_preserving.integrable_comp {ν : measure δ} {g : δ → β}
  {f : α → δ} (hf : measure_preserving f μ ν) (hg : ae_strongly_measurable g ν) :
  integrable (g ∘ f) μ ↔ integrable g ν :=
by { rw ← hf.map_eq at hg ⊢, exact (integrable_map_measure hg hf.measurable.ae_measurable).symm }

lemma measure_preserving.integrable_comp_emb {f : α → δ} {ν} (h₁ : measure_preserving f μ ν)
  (h₂ : measurable_embedding f) {g : δ → β} :
  integrable (g ∘ f) μ ↔ integrable g ν :=
h₁.map_eq ▸ iff.symm h₂.integrable_map_iff

lemma lintegral_edist_lt_top {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) :
  ∫⁻ a, edist (f a) (g a) ∂μ < ∞ :=
lt_of_le_of_lt
  (lintegral_edist_triangle hf.ae_strongly_measurable ae_strongly_measurable_zero)
  (ennreal.add_lt_top.2 $ by { simp_rw [pi.zero_apply, ← has_finite_integral_iff_edist],
                               exact ⟨hf.has_finite_integral, hg.has_finite_integral⟩ })

variables (α β μ)
@[simp] lemma integrable_zero : integrable (λ _, (0 : β)) μ :=
by simp [integrable, ae_strongly_measurable_const]
variables {α β μ}

lemma integrable.add' {f g : α → β} (hf : integrable f μ) (hg : integrable g μ) :
  has_finite_integral (f + g) μ :=
calc ∫⁻ a, ∥f a + g a∥₊ ∂μ ≤ ∫⁻ a, ∥f a∥₊ + ∥g a∥₊ ∂μ :
  lintegral_mono (λ a, by exact_mod_cast nnnorm_add_le _ _)
... = _ : lintegral_nnnorm_add_left hf.ae_strongly_measurable _
... < ∞ : add_lt_top.2 ⟨hf.has_finite_integral, hg.has_finite_integral⟩

lemma integrable.add
  {f g : α → β} (hf : integrable f μ) (hg : integrable g μ) : integrable (f + g) μ :=
⟨hf.ae_strongly_measurable.add hg.ae_strongly_measurable, hf.add' hg⟩

lemma integrable_finset_sum' {ι} (s : finset ι)
  {f : ι → α → β} (hf : ∀ i ∈ s, integrable (f i) μ) : integrable (∑ i in s, f i) μ :=
finset.sum_induction f (λ g, integrable g μ) (λ _ _, integrable.add)
  (integrable_zero _ _ _) hf

lemma integrable_finset_sum {ι} (s : finset ι)
  {f : ι → α → β} (hf : ∀ i ∈ s, integrable (f i) μ) : integrable (λ a, ∑ i in s, f i a) μ :=
by simpa only [← finset.sum_apply] using integrable_finset_sum' s hf

lemma integrable.neg {f : α → β} (hf : integrable f μ) : integrable (-f) μ :=
⟨hf.ae_strongly_measurable.neg, hf.has_finite_integral.neg⟩

@[simp] lemma integrable_neg_iff {f : α → β} :
  integrable (-f) μ ↔ integrable f μ :=
⟨λ h, neg_neg f ▸ h.neg, integrable.neg⟩

lemma integrable.sub {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) : integrable (f - g) μ :=
by simpa only [sub_eq_add_neg] using hf.add hg.neg

lemma integrable.norm {f : α → β} (hf : integrable f μ) :
  integrable (λ a, ∥f a∥) μ :=
⟨hf.ae_strongly_measurable.norm, hf.has_finite_integral.norm⟩

lemma integrable.inf {β} [normed_lattice_add_comm_group β] {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) :
  integrable (f ⊓ g) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf hg ⊢, exact hf.inf hg, }

lemma integrable.sup {β} [normed_lattice_add_comm_group β] {f g : α → β}
  (hf : integrable f μ) (hg : integrable g μ) :
  integrable (f ⊔ g) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf hg ⊢, exact hf.sup hg, }

lemma integrable.abs {β} [normed_lattice_add_comm_group β] {f : α → β} (hf : integrable f μ) :
  integrable (λ a, |f a|) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.abs, }

lemma integrable.bdd_mul {F : Type*} [normed_division_ring F]
  {f g : α → F} (hint : integrable g μ) (hm : ae_strongly_measurable f μ)
  (hfbdd : ∃ C, ∀ x, ∥f x∥ ≤ C) :
  integrable (λ x, f x * g x) μ :=
begin
  casesI is_empty_or_nonempty α with hα hα,
  { rw μ.eq_zero_of_is_empty,
    exact integrable_zero_measure },
  { refine ⟨hm.mul hint.1, _⟩,
    obtain ⟨C, hC⟩ := hfbdd,
    have hCnonneg : 0 ≤ C := le_trans (norm_nonneg _) (hC hα.some),
    have : (λ x, ∥f x * g x∥₊) ≤ λ x, ⟨C, hCnonneg⟩ * ∥g x∥₊,
    { intro x,
      simp only [nnnorm_mul],
      exact mul_le_mul_of_nonneg_right (hC x) (zero_le _) },
    refine lt_of_le_of_lt (lintegral_mono_nnreal this) _,
    simp only [ennreal.coe_mul],
    rw lintegral_const_mul' _ _ ennreal.coe_ne_top,
    exact ennreal.mul_lt_top ennreal.coe_ne_top (ne_of_lt hint.2) },
end

lemma integrable_norm_iff {f : α → β} (hf : ae_strongly_measurable f μ) :
  integrable (λa, ∥f a∥) μ ↔ integrable f μ :=
by simp_rw [integrable, and_iff_right hf, and_iff_right hf.norm, has_finite_integral_norm_iff]

lemma integrable_of_norm_sub_le {f₀ f₁ : α → β} {g : α → ℝ}
  (hf₁_m : ae_strongly_measurable f₁ μ)
  (hf₀_i : integrable f₀ μ)
  (hg_i : integrable g μ)
  (h : ∀ᵐ a ∂μ, ∥f₀ a - f₁ a∥ ≤ g a) :
  integrable f₁ μ :=
begin
  have : ∀ᵐ a ∂μ, ∥f₁ a∥ ≤ ∥f₀ a∥ + g a,
  { apply h.mono,
    intros a ha,
    calc ∥f₁ a∥ ≤ ∥f₀ a∥ + ∥f₀ a - f₁ a∥ : norm_le_insert _ _
    ... ≤ ∥f₀ a∥ + g a : add_le_add_left ha _ },
  exact integrable.mono' (hf₀_i.norm.add hg_i) hf₁_m this
end

lemma integrable.prod_mk {f : α → β} {g : α → γ} (hf : integrable f μ) (hg : integrable g μ) :
  integrable (λ x, (f x, g x)) μ :=
⟨hf.ae_strongly_measurable.prod_mk hg.ae_strongly_measurable,
  (hf.norm.add' hg.norm).mono $ eventually_of_forall $ λ x,
  calc max ∥f x∥ ∥g x∥ ≤ ∥f x∥ + ∥g x∥   : max_le_add_of_nonneg (norm_nonneg _) (norm_nonneg _)
                 ... ≤ ∥(∥f x∥ + ∥g x∥)∥ : le_abs_self _⟩

lemma mem_ℒp.integrable {q : ℝ≥0∞} (hq1 : 1 ≤ q) {f : α → β} [is_finite_measure μ]
  (hfq : mem_ℒp f q μ) : integrable f μ :=
mem_ℒp_one_iff_integrable.mp (hfq.mem_ℒp_of_exponent_le hq1)

lemma lipschitz_with.integrable_comp_iff_of_antilipschitz {K K'} {f : α → β} {g : β → γ}
  (hg : lipschitz_with K g) (hg' : antilipschitz_with K' g) (g0 : g 0 = 0) :
  integrable (g ∘ f) μ ↔ integrable f μ :=
by simp [← mem_ℒp_one_iff_integrable, hg.mem_ℒp_comp_iff_of_antilipschitz hg' g0]

lemma integrable.real_to_nnreal {f : α → ℝ} (hf : integrable f μ) :
  integrable (λ x, ((f x).to_nnreal : ℝ)) μ :=
begin
  refine ⟨hf.ae_strongly_measurable.ae_measurable
    .real_to_nnreal.coe_nnreal_real.ae_strongly_measurable, _⟩,
  rw has_finite_integral_iff_norm,
  refine lt_of_le_of_lt _ ((has_finite_integral_iff_norm _).1 hf.has_finite_integral),
  apply lintegral_mono,
  assume x,
  simp [real.norm_eq_abs, ennreal.of_real_le_of_real, abs_le, abs_nonneg, le_abs_self],
end

lemma of_real_to_real_ae_eq {f : α → ℝ≥0∞} (hf : ∀ᵐ x ∂μ, f x < ∞) :
  (λ x, ennreal.of_real (f x).to_real) =ᵐ[μ] f :=
begin
  filter_upwards [hf],
  assume x hx,
  simp only [hx.ne, of_real_to_real, ne.def, not_false_iff],
end

lemma coe_to_nnreal_ae_eq {f : α → ℝ≥0∞} (hf : ∀ᵐ x ∂μ, f x < ∞) :
  (λ x, ((f x).to_nnreal : ℝ≥0∞)) =ᵐ[μ] f :=
begin
  filter_upwards [hf],
  assume x hx,
  simp only [hx.ne, ne.def, not_false_iff, coe_to_nnreal],
end

section
variables  {E : Type*} [normed_group E] [normed_space ℝ E]

lemma integrable_with_density_iff_integrable_coe_smul
  {f : α → ℝ≥0} (hf : measurable f) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, (f x : ℝ) • g x) μ :=
begin
  by_cases H : ae_strongly_measurable (λ (x : α), (f x : ℝ) • g x) μ,
  { simp only [integrable, ae_strongly_measurable_with_density_iff hf, has_finite_integral, H,
      true_and],
    rw lintegral_with_density_eq_lintegral_mul₀' hf.coe_nnreal_ennreal.ae_measurable,
    { congr',
      ext1 x,
      simp only [nnnorm_smul, nnreal.nnnorm_eq, coe_mul, pi.mul_apply] },
    { rw ae_measurable_with_density_ennreal_iff hf,
      convert H.ennnorm,
      ext1 x,
      simp only [nnnorm_smul, nnreal.nnnorm_eq, coe_mul] } },
  { simp only [integrable, ae_strongly_measurable_with_density_iff hf, H, false_and] }
end

lemma integrable_with_density_iff_integrable_smul {f : α → ℝ≥0} (hf : measurable f) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, f x • g x) μ :=
integrable_with_density_iff_integrable_coe_smul hf

lemma integrable_with_density_iff_integrable_smul'
  {f : α → ℝ≥0∞} (hf : measurable f) (hflt : ∀ᵐ x ∂μ, f x < ∞) {g : α → E} :
  integrable g (μ.with_density f) ↔ integrable (λ x, (f x).to_real • g x) μ :=
begin
  rw [← with_density_congr_ae (coe_to_nnreal_ae_eq hflt),
      integrable_with_density_iff_integrable_smul],
  { refl },
  { exact hf.ennreal_to_nnreal },
end

lemma integrable_with_density_iff_integrable_coe_smul₀
  {f : α → ℝ≥0} (hf : ae_measurable f μ) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, (f x : ℝ) • g x) μ :=
calc
integrable g (μ.with_density (λ x, f x))
    ↔ integrable g (μ.with_density (λ x, hf.mk f x)) :
begin
  suffices : (λ x, (f x : ℝ≥0∞)) =ᵐ[μ] (λ x, hf.mk f x), by rw with_density_congr_ae this,
  filter_upwards [hf.ae_eq_mk] with x hx,
  simp [hx],
end
... ↔ integrable (λ x, (hf.mk f x : ℝ) • g x) μ :
  integrable_with_density_iff_integrable_coe_smul hf.measurable_mk
... ↔ integrable (λ x, (f x : ℝ) • g x) μ :
begin
  apply integrable_congr,
  filter_upwards [hf.ae_eq_mk] with x hx,
  simp [hx],
end

lemma integrable_with_density_iff_integrable_smul₀
  {f : α → ℝ≥0} (hf : ae_measurable f μ) {g : α → E} :
  integrable g (μ.with_density (λ x, f x)) ↔ integrable (λ x, f x • g x) μ :=
integrable_with_density_iff_integrable_coe_smul₀ hf

end

lemma integrable_with_density_iff {f : α → ℝ≥0∞} (hf : measurable f)
  (hflt : ∀ᵐ x ∂μ, f x < ∞) {g : α → ℝ} :
  integrable g (μ.with_density f) ↔ integrable (λ x, g x * (f x).to_real) μ :=
begin
  have : (λ x, g x * (f x).to_real) = (λ x, (f x).to_real • g x), by simp [mul_comm],
  rw this,
  exact integrable_with_density_iff_integrable_smul' hf hflt,
end

section
variables {E : Type*} [normed_group E] [normed_space ℝ E]

lemma mem_ℒ1_smul_of_L1_with_density {f : α → ℝ≥0} (f_meas : measurable f)
  (u : Lp E 1 (μ.with_density (λ x, f x))) :
  mem_ℒp (λ x, f x • u x) 1 μ :=
mem_ℒp_one_iff_integrable.2 $ (integrable_with_density_iff_integrable_smul f_meas).1 $
mem_ℒp_one_iff_integrable.1 (Lp.mem_ℒp u)

variable (μ)
/-- The map `u ↦ f • u` is an isometry between the `L^1` spaces for `μ.with_density f` and `μ`. -/
noncomputable def with_density_smul_li {f : α → ℝ≥0} (f_meas : measurable f) :
  Lp E 1 (μ.with_density (λ x, f x)) →ₗᵢ[ℝ] Lp E 1 μ :=
{ to_fun := λ u, (mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp _,
  map_add' :=
  begin
    assume u v,
    ext1,
    filter_upwards [(mem_ℒ1_smul_of_L1_with_density f_meas u).coe_fn_to_Lp,
      (mem_ℒ1_smul_of_L1_with_density f_meas v).coe_fn_to_Lp,
      (mem_ℒ1_smul_of_L1_with_density f_meas (u + v)).coe_fn_to_Lp,
      Lp.coe_fn_add ((mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp _)
        ((mem_ℒ1_smul_of_L1_with_density f_meas v).to_Lp _),
      (ae_with_density_iff f_meas.coe_nnreal_ennreal).1 (Lp.coe_fn_add u v)],
    assume x hu hv huv h' h'',
    rw [huv, h', pi.add_apply, hu, hv],
    rcases eq_or_ne (f x) 0 with hx|hx,
    { simp only [hx, zero_smul, add_zero] },
    { rw [h'' _, pi.add_apply, smul_add],
      simpa only [ne.def, ennreal.coe_eq_zero] using hx }
  end,
  map_smul' :=
  begin
    assume r u,
    ext1,
    filter_upwards [(ae_with_density_iff f_meas.coe_nnreal_ennreal).1 (Lp.coe_fn_smul r u),
      (mem_ℒ1_smul_of_L1_with_density f_meas (r • u)).coe_fn_to_Lp,
      Lp.coe_fn_smul r ((mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp _),
      (mem_ℒ1_smul_of_L1_with_density f_meas u).coe_fn_to_Lp],
    assume x h h' h'' h''',
    rw [ring_hom.id_apply, h', h'', pi.smul_apply, h'''],
    rcases eq_or_ne (f x) 0 with hx|hx,
    { simp only [hx, zero_smul, smul_zero] },
    { rw [h _, smul_comm, pi.smul_apply],
      simpa only [ne.def, ennreal.coe_eq_zero] using hx }
  end,
  norm_map' :=
  begin
    assume u,
    simp only [snorm, linear_map.coe_mk, Lp.norm_to_Lp, one_ne_zero, ennreal.one_ne_top,
      ennreal.one_to_real, if_false, snorm', ennreal.rpow_one, _root_.div_one, Lp.norm_def],
    rw lintegral_with_density_eq_lintegral_mul_non_measurable _ f_meas.coe_nnreal_ennreal
      (filter.eventually_of_forall (λ x, ennreal.coe_lt_top)),
    congr' 1,
    apply lintegral_congr_ae,
    filter_upwards [(mem_ℒ1_smul_of_L1_with_density f_meas u).coe_fn_to_Lp] with x hx,
    rw [hx, pi.mul_apply],
    change ↑∥(f x : ℝ) • u x∥₊ = ↑(f x) * ↑∥u x∥₊,
    simp only [nnnorm_smul, nnreal.nnnorm_eq, ennreal.coe_mul],
  end }

@[simp] lemma with_density_smul_li_apply {f : α → ℝ≥0} (f_meas : measurable f)
  (u : Lp E 1 (μ.with_density (λ x, f x))) :
  with_density_smul_li μ f_meas u =
    (mem_ℒ1_smul_of_L1_with_density f_meas u).to_Lp (λ x, f x • u x) :=
rfl

end

lemma mem_ℒ1_to_real_of_lintegral_ne_top
  {f : α → ℝ≥0∞} (hfm : ae_measurable f μ) (hfi : ∫⁻ x, f x ∂μ ≠ ∞) :
  mem_ℒp (λ x, (f x).to_real) 1 μ :=
begin
  rw [mem_ℒp, snorm_one_eq_lintegral_nnnorm],
  exact ⟨(ae_measurable.ennreal_to_real hfm).ae_strongly_measurable,
          has_finite_integral_to_real_of_lintegral_ne_top hfi⟩
end

lemma integrable_to_real_of_lintegral_ne_top
  {f : α → ℝ≥0∞} (hfm : ae_measurable f μ) (hfi : ∫⁻ x, f x ∂μ ≠ ∞) :
  integrable (λ x, (f x).to_real) μ :=
mem_ℒp_one_iff_integrable.1 $ mem_ℒ1_to_real_of_lintegral_ne_top hfm hfi

section pos_part
/-! ### Lemmas used for defining the positive part of a `L¹` function -/

lemma integrable.pos_part {f : α → ℝ} (hf : integrable f μ) : integrable (λ a, max (f a) 0) μ :=
⟨(hf.ae_strongly_measurable.ae_measurable.max ae_measurable_const).ae_strongly_measurable,
  hf.has_finite_integral.max_zero⟩

lemma integrable.neg_part {f : α → ℝ} (hf : integrable f μ) : integrable (λ a, max (-f a) 0) μ :=
hf.neg.pos_part

end pos_part

section normed_space
variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma integrable.smul (c : 𝕜) {f : α → β}
  (hf : integrable f μ) : integrable (c • f) μ :=
⟨hf.ae_strongly_measurable.const_smul c, hf.has_finite_integral.smul c⟩

lemma integrable_smul_iff {c : 𝕜} (hc : c ≠ 0) (f : α → β) :
  integrable (c • f) μ ↔ integrable f μ :=
and_congr (ae_strongly_measurable_const_smul_iff₀ hc) (has_finite_integral_smul_iff hc f)

lemma integrable.const_mul {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (λ x, c * f x) μ :=
integrable.smul c h

lemma integrable.const_mul' {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable ((λ (x : α), c) * f) μ :=
integrable.smul c h

lemma integrable.mul_const {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (λ x, f x * c) μ :=
by simp_rw [mul_comm, h.const_mul _]

lemma integrable.mul_const' {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (f * (λ (x : α), c)) μ :=
integrable.mul_const h c

lemma integrable.div_const {f : α → ℝ} (h : integrable f μ) (c : ℝ) :
  integrable (λ x, f x / c) μ :=
by simp_rw [div_eq_mul_inv, h.mul_const]

end normed_space

section normed_space_over_complete_field
variables {𝕜 : Type*} [nondiscrete_normed_field 𝕜] [complete_space 𝕜]
variables {E : Type*} [normed_group E] [normed_space 𝕜 E]

lemma integrable_smul_const {f : α → 𝕜} {c : E} (hc : c ≠ 0) :
  integrable (λ x, f x • c) μ ↔ integrable f μ :=
begin
  simp_rw [integrable, ae_strongly_measurable_smul_const_iff hc, and.congr_right_iff,
    has_finite_integral, nnnorm_smul, ennreal.coe_mul],
  intro hf, rw [lintegral_mul_const' _ _ ennreal.coe_ne_top, ennreal.mul_lt_top_iff],
  have : ∀ x : ℝ≥0∞, x = 0 → x < ∞ := by simp,
  simp [hc, or_iff_left_of_imp (this _)]
end
end normed_space_over_complete_field

section is_R_or_C
variables {𝕜 : Type*} [is_R_or_C 𝕜] {f : α → 𝕜}

lemma integrable.re_im_iff :
  integrable (λ x, is_R_or_C.re (f x)) μ ∧ integrable (λ x, is_R_or_C.im (f x)) μ ↔
  integrable f μ :=
by { simp_rw ← mem_ℒp_one_iff_integrable, exact mem_ℒp_re_im_iff }

lemma integrable.re (hf : integrable f μ) : integrable (λ x, is_R_or_C.re (f x)) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.re, }

lemma integrable.im (hf : integrable f μ) : integrable (λ x, is_R_or_C.im (f x)) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.im, }

end is_R_or_C

section inner_product
variables {𝕜 E : Type*} [is_R_or_C 𝕜] [inner_product_space 𝕜 E] {f : α → E}

local notation `⟪`x`, `y`⟫` := @inner 𝕜 E _ x y

lemma integrable.const_inner (c : E) (hf : integrable f μ) : integrable (λ x, ⟪c, f x⟫) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.const_inner c, }

lemma integrable.inner_const (hf : integrable f μ) (c : E) : integrable (λ x, ⟪f x, c⟫) μ :=
by { rw ← mem_ℒp_one_iff_integrable at hf ⊢, exact hf.inner_const c, }

end inner_product

section trim

variables {H : Type*} [normed_group H] {m0 : measurable_space α} {μ' : measure α} {f : α → H}

lemma integrable.trim (hm : m ≤ m0) (hf_int : integrable f μ') (hf : strongly_measurable[m] f) :
  integrable f (μ'.trim hm) :=
begin
  refine ⟨hf.ae_strongly_measurable, _⟩,
  rw [has_finite_integral, lintegral_trim hm _],
  { exact hf_int.2, },
  { exact @strongly_measurable.ennnorm _ m _ _ f hf },
end

lemma integrable_of_integrable_trim (hm : m ≤ m0) (hf_int : integrable f (μ'.trim hm)) :
  integrable f μ' :=
begin
  obtain ⟨hf_meas_ae, hf⟩ := hf_int,
  refine ⟨ae_strongly_measurable_of_ae_strongly_measurable_trim hm hf_meas_ae, _⟩,
  rw has_finite_integral at hf ⊢,
  rwa lintegral_trim_ae hm _ at hf,
  exact ae_strongly_measurable.ennnorm hf_meas_ae
end

end trim

section sigma_finite

variables {E : Type*} {m0 : measurable_space α} [normed_group E]

lemma integrable_of_forall_fin_meas_le' {μ : measure α} (hm : m ≤ m0) [sigma_finite (μ.trim hm)]
  (C : ℝ≥0∞) (hC : C < ∞) {f : α → E} (hf_meas : ae_strongly_measurable f μ)
  (hf : ∀ s, measurable_set[m] s → μ s ≠ ∞ → ∫⁻ x in s, ∥f x∥₊ ∂μ ≤ C) :
  integrable f μ :=
⟨hf_meas, (lintegral_le_of_forall_fin_meas_le' hm C hf_meas.ennnorm hf).trans_lt hC⟩

lemma integrable_of_forall_fin_meas_le [sigma_finite μ]
  (C : ℝ≥0∞) (hC : C < ∞) {f : α → E} (hf_meas : ae_strongly_measurable f μ)
  (hf : ∀ s : set α, measurable_set s → μ s ≠ ∞ → ∫⁻ x in s, ∥f x∥₊ ∂μ ≤ C) :
  integrable f μ :=
@integrable_of_forall_fin_meas_le' _ _ _ _ _ _ _ (by rwa trim_eq_self) C hC _ hf_meas hf

end sigma_finite

/-! ### The predicate `integrable` on measurable functions modulo a.e.-equality -/

namespace ae_eq_fun

section

/-- A class of almost everywhere equal functions is `integrable` if its function representative
is integrable. -/
def integrable (f : α →ₘ[μ] β) : Prop := integrable f μ

lemma integrable_mk {f : α → β} (hf : ae_strongly_measurable f μ ) :
  (integrable (mk f hf : α →ₘ[μ] β)) ↔ measure_theory.integrable f μ :=
begin
  simp [integrable],
  apply integrable_congr,
  exact coe_fn_mk f hf
end

lemma integrable_coe_fn {f : α →ₘ[μ] β} : (measure_theory.integrable f μ) ↔ integrable f :=
by rw [← integrable_mk, mk_coe_fn]

lemma integrable_zero : integrable (0 : α →ₘ[μ] β) :=
(integrable_zero α β μ).congr (coe_fn_mk _ _).symm

end

section

lemma integrable.neg {f : α →ₘ[μ] β} : integrable f → integrable (-f) :=
induction_on f $ λ f hfm hfi, (integrable_mk _).2 ((integrable_mk hfm).1 hfi).neg

section

lemma integrable_iff_mem_L1 {f : α →ₘ[μ] β} : integrable f ↔ f ∈ (α →₁[μ] β) :=
by rw [← integrable_coe_fn, ← mem_ℒp_one_iff_integrable, Lp.mem_Lp_iff_mem_ℒp]

lemma integrable.add {f g : α →ₘ[μ] β} : integrable f → integrable g → integrable (f + g) :=
begin
  refine induction_on₂ f g (λ f hf g hg hfi hgi, _),
  simp only [integrable_mk, mk_add_mk] at hfi hgi ⊢,
  exact hfi.add hgi
end

lemma integrable.sub {f g : α →ₘ[μ] β} (hf : integrable f) (hg : integrable g) :
  integrable (f - g) :=
(sub_eq_add_neg f g).symm ▸ hf.add hg.neg

end

section normed_space
variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma integrable.smul {c : 𝕜} {f : α →ₘ[μ] β} : integrable f → integrable (c • f) :=
induction_on f $ λ f hfm hfi, (integrable_mk _).2 $ ((integrable_mk hfm).1 hfi).smul _

end normed_space

end

end ae_eq_fun

namespace L1

lemma integrable_coe_fn (f : α →₁[μ] β) :
  integrable f μ :=
by { rw ← mem_ℒp_one_iff_integrable, exact Lp.mem_ℒp f }

lemma has_finite_integral_coe_fn (f : α →₁[μ] β) :
  has_finite_integral f μ :=
(integrable_coe_fn f).has_finite_integral

lemma strongly_measurable_coe_fn (f : α →₁[μ] β) : strongly_measurable f :=
Lp.strongly_measurable f

lemma measurable_coe_fn [measurable_space β] [borel_space β] (f : α →₁[μ] β) :
  measurable f :=
(Lp.strongly_measurable f).measurable

lemma ae_strongly_measurable_coe_fn (f : α →₁[μ] β) : ae_strongly_measurable f μ :=
Lp.ae_strongly_measurable f

lemma ae_measurable_coe_fn [measurable_space β] [borel_space β] (f : α →₁[μ] β) :
  ae_measurable f μ :=
(Lp.strongly_measurable f).measurable.ae_measurable

lemma edist_def (f g : α →₁[μ] β) :
  edist f g = ∫⁻ a, edist (f a) (g a) ∂μ :=
by { simp [Lp.edist_def, snorm, snorm'], simp [edist_eq_coe_nnnorm_sub] }

lemma dist_def (f g : α →₁[μ] β) :
  dist f g = (∫⁻ a, edist (f a) (g a) ∂μ).to_real :=
by { simp [Lp.dist_def, snorm, snorm'], simp [edist_eq_coe_nnnorm_sub] }

lemma norm_def (f : α →₁[μ] β) :
  ∥f∥ = (∫⁻ a, ∥f a∥₊ ∂μ).to_real :=
by { simp [Lp.norm_def, snorm, snorm'] }

/-- Computing the norm of a difference between two L¹-functions. Note that this is not a
  special case of `norm_def` since `(f - g) x` and `f x - g x` are not equal
  (but only a.e.-equal). -/
lemma norm_sub_eq_lintegral (f g : α →₁[μ] β) :
  ∥f - g∥ = (∫⁻ x, (∥f x - g x∥₊ : ℝ≥0∞) ∂μ).to_real :=
begin
  rw [norm_def],
  congr' 1,
  rw lintegral_congr_ae,
  filter_upwards [Lp.coe_fn_sub f g] with _ ha,
  simp only [ha, pi.sub_apply],
end

lemma of_real_norm_eq_lintegral (f : α →₁[μ] β) :
  ennreal.of_real ∥f∥ = ∫⁻ x, (∥f x∥₊ : ℝ≥0∞) ∂μ :=
by { rw [norm_def, ennreal.of_real_to_real], exact ne_of_lt (has_finite_integral_coe_fn f) }

/-- Computing the norm of a difference between two L¹-functions. Note that this is not a
  special case of `of_real_norm_eq_lintegral` since `(f - g) x` and `f x - g x` are not equal
  (but only a.e.-equal). -/
lemma of_real_norm_sub_eq_lintegral (f g : α →₁[μ] β) :
  ennreal.of_real ∥f - g∥ = ∫⁻ x, (∥f x - g x∥₊ : ℝ≥0∞) ∂μ :=
begin
  simp_rw [of_real_norm_eq_lintegral, ← edist_eq_coe_nnnorm],
  apply lintegral_congr_ae,
  filter_upwards [Lp.coe_fn_sub f g] with _ ha,
  simp only [ha, pi.sub_apply],
end

end L1

namespace integrable

/-- Construct the equivalence class `[f]` of an integrable function `f`, as a member of the
space `L1 β 1 μ`. -/
def to_L1 (f : α → β) (hf : integrable f μ) : α →₁[μ] β :=
(mem_ℒp_one_iff_integrable.2 hf).to_Lp f

@[simp] lemma to_L1_coe_fn (f : α →₁[μ] β) (hf : integrable f μ) : hf.to_L1 f = f :=
by simp [integrable.to_L1]

lemma coe_fn_to_L1 {f : α → β} (hf : integrable f μ) : hf.to_L1 f =ᵐ[μ] f :=
ae_eq_fun.coe_fn_mk _ _

@[simp] lemma to_L1_zero (h : integrable (0 : α → β) μ) : h.to_L1 0 = 0 := rfl

@[simp] lemma to_L1_eq_mk (f : α → β) (hf : integrable f μ) :
  (hf.to_L1 f : α →ₘ[μ] β) = ae_eq_fun.mk f hf.ae_strongly_measurable :=
rfl

@[simp] lemma to_L1_eq_to_L1_iff (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  to_L1 f hf = to_L1 g hg ↔ f =ᵐ[μ] g :=
mem_ℒp.to_Lp_eq_to_Lp_iff _ _

lemma to_L1_add (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  to_L1 (f + g) (hf.add hg) = to_L1 f hf + to_L1 g hg := rfl

lemma to_L1_neg (f : α → β) (hf : integrable f μ) :
  to_L1 (- f) (integrable.neg hf) = - to_L1 f hf := rfl

lemma to_L1_sub (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  to_L1 (f - g) (hf.sub hg) = to_L1 f hf - to_L1 g hg := rfl

lemma norm_to_L1 (f : α → β) (hf : integrable f μ) :
  ∥hf.to_L1 f∥ = ennreal.to_real (∫⁻ a, edist (f a) 0 ∂μ) :=
by { simp [to_L1, snorm, snorm'], simp [edist_eq_coe_nnnorm] }

lemma norm_to_L1_eq_lintegral_norm (f : α → β) (hf : integrable f μ) :
  ∥hf.to_L1 f∥ = ennreal.to_real (∫⁻ a, (ennreal.of_real ∥f a∥) ∂μ) :=
by { rw [norm_to_L1, lintegral_norm_eq_lintegral_edist] }

@[simp] lemma edist_to_L1_to_L1 (f g : α → β) (hf : integrable f μ) (hg : integrable g μ) :
  edist (hf.to_L1 f) (hg.to_L1 g) = ∫⁻ a, edist (f a) (g a) ∂μ :=
by { simp [integrable.to_L1, snorm, snorm'], simp [edist_eq_coe_nnnorm_sub] }

@[simp] lemma edist_to_L1_zero (f : α → β) (hf : integrable f μ) :
  edist (hf.to_L1 f) 0 = ∫⁻ a, edist (f a) 0 ∂μ :=
by { simp [integrable.to_L1, snorm, snorm'], simp [edist_eq_coe_nnnorm] }

variables {𝕜 : Type*} [normed_field 𝕜] [normed_space 𝕜 β]

lemma to_L1_smul (f : α → β) (hf : integrable f μ) (k : 𝕜) :
  to_L1 (λ a, k • f a) (hf.smul k) = k • to_L1 f hf := rfl

lemma to_L1_smul' (f : α → β) (hf : integrable f μ) (k : 𝕜) :
  to_L1 (k • f) (hf.smul k) = k • to_L1 f hf := rfl

end integrable

end measure_theory

open measure_theory

variables {E : Type*} [normed_group E]
          {𝕜 : Type*} [nondiscrete_normed_field 𝕜] [normed_space 𝕜 E]
          {H : Type*} [normed_group H] [normed_space 𝕜 H]

lemma measure_theory.integrable.apply_continuous_linear_map {φ : α → H →L[𝕜] E}
  (φ_int : integrable φ μ) (v : H) : integrable (λ a, φ a v) μ :=
(φ_int.norm.mul_const ∥v∥).mono' (φ_int.ae_strongly_measurable.apply_continuous_linear_map v)
  (eventually_of_forall $ λ a, (φ a).le_op_norm v)

lemma continuous_linear_map.integrable_comp {φ : α → H} (L : H →L[𝕜] E)
  (φ_int : integrable φ μ) : integrable (λ (a : α), L (φ a)) μ :=
((integrable.norm φ_int).const_mul ∥L∥).mono'
  (L.continuous.comp_ae_strongly_measurable φ_int.ae_strongly_measurable)
  (eventually_of_forall $ λ a, L.le_op_norm (φ a))


import tactic

open function

theorem challenge4 (X Y Z : Type) (f : X → Y) (g : Y → Z) : surjective (g ∘ f) → surjective g :=
begin
  intro h,
  intro z,
  cases h z with a ha,
  use f a,
  assumption,
end