/-
Copyright (c) 2021 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import algebra.indicator_function
import topology.algebra.group
import topology.continuous_on
import topology.instances.ennreal

/-!
# Semicontinuous maps

A function `f` from a topological space `α` to an ordered space `β` is lower semicontinuous at a
point `x` if, for any `y < f x`, for any `x'` close enough to `x`, one has `f x' > y`. In other
words, `f` can jump up, but it can not jump down.

Upper semicontinuous functions are defined similarly.

This file introduces these notions, and a basic API around them mimicking the API for continuous
functions.

## Main definitions and results

We introduce 4 definitions related to lower semicontinuity:
* `lower_semicontinuous_within_at f s x`
* `lower_semicontinuous_at f x`
* `lower_semicontinuous_on f s`
* `lower_semicontinuous f`

We build a basic API using dot notation around these notions, and we prove that
* constant functions are lower semicontinuous;
* `indicator s (λ _, y)` is lower semicontinuous when `s` is open and `0 ≤ y`, or when `s` is closed
  and `y ≤ 0`;
* continuous functions are lower semicontinuous;
* composition with a continuous monotone functions maps lower semicontinuous functions to lower
  semicontinuous functions. If the function is anti-monotone, it instead maps lower semicontinuous
  functions to upper semicontinuous functions;
* a sum of two (or finitely many) lower semicontinuous functions is lower semicontinuous;
* a supremum of a family of lower semicontinuous functions is lower semicontinuous;
* An infinite sum of `ℝ≥0∞`-valued lower semicontinuous functions is lower semicontinuous.

Similar results are stated and proved for upper semicontinuity.

We also prove that a function is continuous if and only if it is both lower and upper
semicontinuous.

## Implementation details

All the nontrivial results for upper semicontinuous functions are deduced from the corresponding
ones for lower semicontinuous functions using `order_dual`.

-/

open_locale topological_space big_operators ennreal
open set

variables {α : Type*} [topological_space α] {β : Type*} [preorder β]
{f g : α → β} {x : α} {s t : set α} {y z : β}

/-! ### Main definitions -/

/-- A real function `f` is lower semicontinuous at `x` within a set `s` if, for any `ε > 0`, for all
`x'` close enough to `x` in  `s`, then `f x'` is at least `f x - ε`. We formulate this in a general
preordered space, using an arbitrary `y < f x` instead of `f x - ε`. -/
def lower_semicontinuous_within_at (f : α → β) (s : set α) (x : α) :=
∀ y < f x, ∀ᶠ x' in 𝓝[s] x, y < f x'

/-- A real function `f` is lower semicontinuous on a set `s` if, for any `ε > 0`, for any `x ∈ s`,
for all `x'` close enough to `x` in `s`, then `f x'` is at least `f x - ε`. We formulate this in
a general preordered space, using an arbitrary `y < f x` instead of `f x - ε`.-/
def lower_semicontinuous_on (f : α → β) (s : set α) :=
∀ x ∈ s, lower_semicontinuous_within_at f s x

/-- A real function `f` is lower semicontinuous at `x` if, for any `ε > 0`, for all `x'` close
enough to `x`, then `f x'` is at least `f x - ε`. We formulate this in a general preordered space,
using an arbitrary `y < f x` instead of `f x - ε`. -/
def lower_semicontinuous_at (f : α → β) (x : α) :=
∀ y < f x, ∀ᶠ x' in 𝓝 x, y < f x'

/-- A real function `f` is lower semicontinuous if, for any `ε > 0`, for any `x`, for all `x'` close
enough to `x`, then `f x'` is at least `f x - ε`. We formulate this in a general preordered space,
using an arbitrary `y < f x` instead of `f x - ε`. -/
def lower_semicontinuous (f : α → β) :=
∀ x, lower_semicontinuous_at f x

/-- A real function `f` is upper semicontinuous at `x` within a set `s` if, for any `ε > 0`, for all
`x'` close enough to `x` in  `s`, then `f x'` is at most `f x + ε`. We formulate this in a general
preordered space, using an arbitrary `y > f x` instead of `f x + ε`. -/
def upper_semicontinuous_within_at (f : α → β) (s : set α) (x : α) :=
∀ y, f x < y → ∀ᶠ x' in 𝓝[s] x, f x' < y

/-- A real function `f` is upper semicontinuous on a set `s` if, for any `ε > 0`, for any `x ∈ s`,
for all `x'` close enough to `x` in `s`, then `f x'` is at most `f x + ε`. We formulate this in a
general preordered space, using an arbitrary `y > f x` instead of `f x + ε`.-/
def upper_semicontinuous_on (f : α → β) (s : set α) :=
∀ x ∈ s, upper_semicontinuous_within_at f s x

/-- A real function `f` is upper semicontinuous at `x` if, for any `ε > 0`, for all `x'` close
enough to `x`, then `f x'` is at most `f x + ε`. We formulate this in a general preordered space,
using an arbitrary `y > f x` instead of `f x + ε`. -/
def upper_semicontinuous_at (f : α → β) (x : α) :=
∀ y, f x < y → ∀ᶠ x' in 𝓝 x, f x' < y

/-- A real function `f` is upper semicontinuous if, for any `ε > 0`, for any `x`, for all `x'`
close enough to `x`, then `f x'` is at most `f x + ε`. We formulate this in a general preordered
space, using an arbitrary `y > f x` instead of `f x + ε`.-/
def upper_semicontinuous (f : α → β) :=
∀ x, upper_semicontinuous_at f x

/-!
### Lower semicontinuous functions
-/

/-! #### Basic dot notation interface for lower semicontinuity -/

lemma lower_semicontinuous_within_at.mono (h : lower_semicontinuous_within_at f s x)
  (hst : t ⊆ s) : lower_semicontinuous_within_at f t x :=
λ y hy, filter.eventually.filter_mono (nhds_within_mono _ hst) (h y hy)

lemma lower_semicontinuous_within_at_univ_iff :
  lower_semicontinuous_within_at f univ x ↔ lower_semicontinuous_at f x :=
by simp [lower_semicontinuous_within_at, lower_semicontinuous_at, nhds_within_univ]

lemma lower_semicontinuous_at.lower_semicontinuous_within_at
  (s : set α) (h : lower_semicontinuous_at f x) : lower_semicontinuous_within_at f s x :=
λ y hy, filter.eventually.filter_mono nhds_within_le_nhds (h y hy)

lemma lower_semicontinuous_on.lower_semicontinuous_within_at
  (h : lower_semicontinuous_on f s) (hx : x ∈ s) :
  lower_semicontinuous_within_at f s x :=
h x hx

lemma lower_semicontinuous_on.mono (h : lower_semicontinuous_on f s) (hst : t ⊆ s) :
  lower_semicontinuous_on f t :=
λ x hx, (h x (hst hx)).mono hst

lemma lower_semicontinuous_on_univ_iff :
  lower_semicontinuous_on f univ ↔ lower_semicontinuous f :=
by simp [lower_semicontinuous_on, lower_semicontinuous, lower_semicontinuous_within_at_univ_iff]

lemma lower_semicontinuous.lower_semicontinuous_at
  (h : lower_semicontinuous f) (x : α) : lower_semicontinuous_at f x :=
h x

lemma lower_semicontinuous.lower_semicontinuous_within_at
  (h : lower_semicontinuous f) (s : set α) (x : α) : lower_semicontinuous_within_at f s x :=
(h x).lower_semicontinuous_within_at s

lemma lower_semicontinuous.lower_semicontinuous_on
  (h : lower_semicontinuous f) (s : set α) : lower_semicontinuous_on f s :=
λ x hx, h.lower_semicontinuous_within_at s x

/-! #### Constants -/

lemma lower_semicontinuous_within_at_const :
  lower_semicontinuous_within_at (λ x, z) s x :=
λ y hy, filter.eventually_of_forall (λ x, hy)

lemma lower_semicontinuous_at_const :
  lower_semicontinuous_at (λ x, z) x :=
λ y hy, filter.eventually_of_forall (λ x, hy)

lemma lower_semicontinuous_on_const :
  lower_semicontinuous_on (λ x, z) s :=
λ x hx, lower_semicontinuous_within_at_const

lemma lower_semicontinuous_const :
  lower_semicontinuous (λ (x : α), z) :=
λ x, lower_semicontinuous_at_const

/-! #### Indicators -/

section
variables [has_zero β]

lemma is_open.lower_semicontinuous_indicator (hs : is_open s) (hy : 0 ≤ y) :
  lower_semicontinuous (indicator s (λ x, y)) :=
begin
  assume x z hz,
  by_cases h : x ∈ s; simp [h] at hz,
  { filter_upwards [hs.mem_nhds h],
    simp [hz] { contextual := tt} },
  { apply filter.eventually_of_forall (λ x', _),
    by_cases h' : x' ∈ s;
    simp [h', hz.trans_le hy, hz] }
end

lemma is_open.lower_semicontinuous_on_indicator (hs : is_open s) (hy : 0 ≤ y) :
  lower_semicontinuous_on (indicator s (λ x, y)) t :=
(hs.lower_semicontinuous_indicator hy).lower_semicontinuous_on t

lemma is_open.lower_semicontinuous_at_indicator (hs : is_open s) (hy : 0 ≤ y) :
  lower_semicontinuous_at (indicator s (λ x, y)) x :=
(hs.lower_semicontinuous_indicator hy).lower_semicontinuous_at x

lemma is_open.lower_semicontinuous_within_at_indicator (hs : is_open s) (hy : 0 ≤ y) :
  lower_semicontinuous_within_at (indicator s (λ x, y)) t x :=
(hs.lower_semicontinuous_indicator hy).lower_semicontinuous_within_at t x

lemma is_closed.lower_semicontinuous_indicator (hs : is_closed s) (hy : y ≤ 0) :
  lower_semicontinuous (indicator s (λ x, y)) :=
begin
  assume x z hz,
  by_cases h : x ∈ s; simp [h] at hz,
  { apply filter.eventually_of_forall (λ x', _),
    by_cases h' : x' ∈ s;
    simp [h', hz, hz.trans_le hy], },
  { filter_upwards [hs.is_open_compl.mem_nhds h],
    simp [hz] { contextual := tt } }
end

lemma is_closed.lower_semicontinuous_on_indicator (hs : is_closed s) (hy : y ≤ 0) :
  lower_semicontinuous_on (indicator s (λ x, y)) t :=
(hs.lower_semicontinuous_indicator hy).lower_semicontinuous_on t

lemma is_closed.lower_semicontinuous_at_indicator (hs : is_closed s) (hy : y ≤ 0) :
  lower_semicontinuous_at (indicator s (λ x, y)) x :=
(hs.lower_semicontinuous_indicator hy).lower_semicontinuous_at x

lemma is_closed.lower_semicontinuous_within_at_indicator (hs : is_closed s) (hy : y ≤ 0) :
  lower_semicontinuous_within_at (indicator s (λ x, y)) t x :=
(hs.lower_semicontinuous_indicator hy).lower_semicontinuous_within_at t x

end

/-! #### Relationship with continuity -/

theorem lower_semicontinuous_iff_is_open :
  lower_semicontinuous f ↔ ∀ y, is_open (f ⁻¹' (Ioi y)) :=
⟨λ H y, is_open_iff_mem_nhds.2 (λ x hx, H x y hx), λ H x y y_lt, is_open.mem_nhds (H y) y_lt⟩

lemma lower_semicontinuous.is_open_preimage (hf : lower_semicontinuous f) (y : β) :
  is_open (f ⁻¹' (Ioi y)) :=
lower_semicontinuous_iff_is_open.1 hf y

section
variables {γ : Type*} [linear_order γ] [topological_space γ] [order_topology γ]

lemma continuous_within_at.lower_semicontinuous_within_at {f : α → γ}
  (h : continuous_within_at f s x) : lower_semicontinuous_within_at f s x :=
λ y hy, h (Ioi_mem_nhds hy)

lemma continuous_at.lower_semicontinuous_at {f : α → γ}
  (h : continuous_at f x) : lower_semicontinuous_at f x :=
λ y hy, h (Ioi_mem_nhds hy)

lemma continuous_on.lower_semicontinuous_on {f : α → γ}
  (h : continuous_on f s) : lower_semicontinuous_on f s :=
λ x hx, (h x hx).lower_semicontinuous_within_at

lemma continuous.lower_semicontinuous {f : α → γ}
  (h : continuous f) : lower_semicontinuous f :=
λ x, h.continuous_at.lower_semicontinuous_at

end

/-! ### Composition -/

section
variables {γ : Type*} [linear_order γ] [topological_space γ] [order_topology γ]
variables {δ : Type*} [linear_order δ] [topological_space δ] [order_topology δ]

lemma continuous_at.comp_lower_semicontinuous_within_at
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : lower_semicontinuous_within_at f s x)
  (gmon : monotone g) : lower_semicontinuous_within_at (g ∘ f) s x :=
begin
  assume y hy,
  by_cases h : ∃ l, l < f x,
  { obtain ⟨z, zlt, hz⟩ : ∃ z < f x, Ioc z (f x) ⊆ g ⁻¹' (Ioi y) :=
      exists_Ioc_subset_of_mem_nhds (hg (Ioi_mem_nhds hy)) h,
    filter_upwards [hf z zlt],
    assume a ha,
    calc y < g (min (f x) (f a)) : hz (by simp [zlt, ha, le_refl])
    ... ≤ g (f a) : gmon (min_le_right _ _) },
  { simp only [not_exists, not_lt] at h,
    exact filter.eventually_of_forall (λ a, hy.trans_le (gmon (h (f a)))) }
end

lemma continuous_at.comp_lower_semicontinuous_at
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : lower_semicontinuous_at f x)
  (gmon : monotone g) : lower_semicontinuous_at (g ∘ f) x :=
begin
  simp only [← lower_semicontinuous_within_at_univ_iff] at hf ⊢,
  exact hg.comp_lower_semicontinuous_within_at hf gmon
end

lemma continuous.comp_lower_semicontinuous_on
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : lower_semicontinuous_on f s)
  (gmon : monotone g) : lower_semicontinuous_on (g ∘ f) s :=
λ x hx, (hg.continuous_at).comp_lower_semicontinuous_within_at (hf x hx) gmon

lemma continuous.comp_lower_semicontinuous
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : lower_semicontinuous f)
  (gmon : monotone g) : lower_semicontinuous (g ∘ f) :=
λ x, (hg.continuous_at).comp_lower_semicontinuous_at (hf x) gmon

lemma continuous_at.comp_lower_semicontinuous_within_at_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : lower_semicontinuous_within_at f s x)
  (gmon : antitone g) : upper_semicontinuous_within_at (g ∘ f) s x :=
@continuous_at.comp_lower_semicontinuous_within_at α _ x s γ _ _ _ (order_dual δ) _ _ _
  g f hg hf gmon

lemma continuous_at.comp_lower_semicontinuous_at_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : lower_semicontinuous_at f x)
  (gmon : antitone g) : upper_semicontinuous_at (g ∘ f) x :=
@continuous_at.comp_lower_semicontinuous_at α _ x γ _ _ _ (order_dual δ) _ _ _ g f hg hf gmon

lemma continuous.comp_lower_semicontinuous_on_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : lower_semicontinuous_on f s)
  (gmon : antitone g) : upper_semicontinuous_on (g ∘ f) s :=
λ x hx, (hg.continuous_at).comp_lower_semicontinuous_within_at_antitone (hf x hx) gmon

lemma continuous.comp_lower_semicontinuous_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : lower_semicontinuous f)
  (gmon : antitone g) : upper_semicontinuous (g ∘ f) :=
λ x, (hg.continuous_at).comp_lower_semicontinuous_at_antitone (hf x) gmon

end

/-! #### Addition -/

section
variables {ι : Type*} {γ : Type*} [linear_ordered_add_comm_monoid γ]
[topological_space γ] [order_topology γ]

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma lower_semicontinuous_within_at.add' {f g : α → γ}
  (hf : lower_semicontinuous_within_at f s x) (hg : lower_semicontinuous_within_at g s x)
  (hcont : continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  lower_semicontinuous_within_at (λ z, f z + g z) s x :=
begin
  assume y hy,
  obtain ⟨u, v, u_open, xu, v_open, xv, h⟩ : ∃ (u v : set γ), is_open u ∧ f x ∈ u ∧ is_open v ∧
    g x ∈ v ∧ u.prod v ⊆ {p : γ × γ | y < p.fst + p.snd} :=
  mem_nhds_prod_iff'.1 (hcont (is_open_Ioi.mem_nhds hy)),
  by_cases hx₁ : ∃ l, l < f x,
  { obtain ⟨z₁, z₁lt, h₁⟩ : ∃ z₁ < f x, Ioc z₁ (f x) ⊆ u :=
      exists_Ioc_subset_of_mem_nhds (u_open.mem_nhds xu) hx₁,
    by_cases hx₂ : ∃ l, l < g x,
    { obtain ⟨z₂, z₂lt, h₂⟩ : ∃ z₂ < g x, Ioc z₂ (g x) ⊆ v :=
        exists_Ioc_subset_of_mem_nhds (v_open.mem_nhds xv) hx₂,
      filter_upwards [hf z₁ z₁lt, hg z₂ z₂lt],
      assume z h₁z h₂z,
      have A1 : min (f z) (f x) ∈ u,
      { by_cases H : f z ≤ f x,
        { simp [H], exact h₁ ⟨h₁z, H⟩ },
        { simp [le_of_not_le H], exact h₁ ⟨z₁lt, le_refl _⟩, } },
      have A2 : min (g z) (g x) ∈ v,
      { by_cases H : g z ≤ g x,
        { simp [H], exact h₂ ⟨h₂z, H⟩ },
        { simp [le_of_not_le H], exact h₂ ⟨z₂lt, le_refl _⟩, } },
      have : (min (f z) (f x), min (g z) (g x)) ∈ u.prod v := ⟨A1, A2⟩,
      calc y < min (f z) (f x) + min (g z) (g x) : h this
      ... ≤ f z + g z : add_le_add (min_le_left _ _) (min_le_left _ _) },
    { simp only [not_exists, not_lt] at hx₂,
      filter_upwards [hf z₁ z₁lt],
      assume z h₁z,
      have A1 : min (f z) (f x) ∈ u,
      { by_cases H : f z ≤ f x,
        { simp [H], exact h₁ ⟨h₁z, H⟩ },
        { simp [le_of_not_le H], exact h₁ ⟨z₁lt, le_refl _⟩, } },
      have : (min (f z) (f x), g x) ∈ u.prod v := ⟨A1, xv⟩,
      calc y < min (f z) (f x) + g x : h this
      ... ≤ f z + g z : add_le_add (min_le_left _ _) (hx₂ (g z)) } },
  { simp only [not_exists, not_lt] at hx₁,
    by_cases hx₂ : ∃ l, l < g x,
    { obtain ⟨z₂, z₂lt, h₂⟩ : ∃ z₂ < g x, Ioc z₂ (g x) ⊆ v :=
        exists_Ioc_subset_of_mem_nhds (v_open.mem_nhds xv) hx₂,
      filter_upwards [hg z₂ z₂lt],
      assume z h₂z,
      have A2 : min (g z) (g x) ∈ v,
      { by_cases H : g z ≤ g x,
        { simp [H], exact h₂ ⟨h₂z, H⟩ },
        { simp [le_of_not_le H], exact h₂ ⟨z₂lt, le_refl _⟩, } },
      have : (f x, min (g z) (g x)) ∈ u.prod v := ⟨xu, A2⟩,
      calc y < f x + min (g z) (g x) : h this
      ... ≤ f z + g z : add_le_add (hx₁ (f z)) (min_le_left _ _) },
    { simp only [not_exists, not_lt] at hx₁ hx₂,
      apply filter.eventually_of_forall,
      assume z,
      have : (f x, g x) ∈ u.prod v := ⟨xu, xv⟩,
      calc y < f x + g x : h this
      ... ≤ f z + g z : add_le_add (hx₁ (f z)) (hx₂ (g z)) } },
end

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma lower_semicontinuous_at.add' {f g : α → γ}
  (hf : lower_semicontinuous_at f x) (hg : lower_semicontinuous_at g x)
  (hcont : continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  lower_semicontinuous_at (λ z, f z + g z) x :=
by { simp_rw [← lower_semicontinuous_within_at_univ_iff] at *, exact hf.add' hg hcont }

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma lower_semicontinuous_on.add' {f g : α → γ}
  (hf : lower_semicontinuous_on f s) (hg : lower_semicontinuous_on g s)
  (hcont : ∀ x ∈ s, continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  lower_semicontinuous_on (λ z, f z + g z) s :=
λ x hx, (hf x hx).add' (hg x hx) (hcont x hx)

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma lower_semicontinuous.add' {f g : α → γ}
  (hf : lower_semicontinuous f) (hg : lower_semicontinuous g)
  (hcont : ∀ x, continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  lower_semicontinuous (λ z, f z + g z) :=
λ x, (hf x).add' (hg x) (hcont x)

variable [has_continuous_add γ]

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma lower_semicontinuous_within_at.add {f g : α → γ}
  (hf : lower_semicontinuous_within_at f s x) (hg : lower_semicontinuous_within_at g s x) :
  lower_semicontinuous_within_at (λ z, f z + g z) s x :=
hf.add' hg continuous_add.continuous_at

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma lower_semicontinuous_at.add {f g : α → γ}
  (hf : lower_semicontinuous_at f x) (hg : lower_semicontinuous_at g x) :
  lower_semicontinuous_at (λ z, f z + g z) x :=
hf.add' hg continuous_add.continuous_at

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma lower_semicontinuous_on.add {f g : α → γ}
  (hf : lower_semicontinuous_on f s) (hg : lower_semicontinuous_on g s) :
  lower_semicontinuous_on (λ z, f z + g z) s :=
hf.add' hg (λ x hx, continuous_add.continuous_at)

/-- The sum of two lower semicontinuous functions is lower semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma lower_semicontinuous.add {f g : α → γ}
  (hf : lower_semicontinuous f) (hg : lower_semicontinuous g) :
  lower_semicontinuous (λ z, f z + g z) :=
hf.add' hg (λ x, continuous_add.continuous_at)

lemma lower_semicontinuous_within_at_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, lower_semicontinuous_within_at (f i) s x) :
  lower_semicontinuous_within_at (λ z, (∑ i in a, f i z)) s x :=
begin
  classical,
  induction a using finset.induction_on with i a ia IH generalizing ha,
  { exact lower_semicontinuous_within_at_const },
  { simp only [ia, finset.sum_insert, not_false_iff],
    exact lower_semicontinuous_within_at.add (ha _ (finset.mem_insert_self i a))
      (IH (λ j ja, ha j (finset.mem_insert_of_mem ja))) }
end

lemma lower_semicontinuous_at_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, lower_semicontinuous_at (f i) x) :
  lower_semicontinuous_at (λ z, (∑ i in a, f i z)) x :=
begin
  simp_rw [← lower_semicontinuous_within_at_univ_iff] at *,
  exact lower_semicontinuous_within_at_sum ha
end

lemma lower_semicontinuous_on_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, lower_semicontinuous_on (f i) s) :
  lower_semicontinuous_on (λ z, (∑ i in a, f i z)) s :=
λ x hx, lower_semicontinuous_within_at_sum (λ i hi, ha i hi x hx)

lemma lower_semicontinuous_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, lower_semicontinuous (f i)) :
  lower_semicontinuous (λ z, (∑ i in a, f i z)) :=
λ x, lower_semicontinuous_at_sum (λ i hi, ha i hi x)

end

/-! #### Supremum -/

section
variables {ι : Sort*} {δ : Type*} [complete_linear_order δ]

lemma lower_semicontinuous_within_at_supr {f : ι → α → δ}
  (h : ∀ i, lower_semicontinuous_within_at (f i) s x) :
  lower_semicontinuous_within_at (λ x', ⨆ i, f i x') s x :=
begin
  assume y hy,
  rcases lt_supr_iff.1 hy with ⟨i, hi⟩,
  filter_upwards [h i y hi],
  assume x' hx',
  exact lt_supr_iff.2 ⟨i, hx'⟩,
end

lemma lower_semicontinuous_within_at_bsupr {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, lower_semicontinuous_within_at (f i hi) s x) :
  lower_semicontinuous_within_at (λ x', ⨆ i hi, f i hi x') s x :=
lower_semicontinuous_within_at_supr $ λ i, lower_semicontinuous_within_at_supr $ λ hi, h i hi

lemma lower_semicontinuous_at_supr {f : ι → α → δ}
  (h : ∀ i, lower_semicontinuous_at (f i) x) :
  lower_semicontinuous_at (λ x', ⨆ i, f i x') x :=
begin
  simp_rw [← lower_semicontinuous_within_at_univ_iff] at *,
  exact lower_semicontinuous_within_at_supr h
end

lemma lower_semicontinuous_at_bsupr {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, lower_semicontinuous_at (f i hi) x) :
  lower_semicontinuous_at (λ x', ⨆ i hi, f i hi x') x :=
lower_semicontinuous_at_supr $ λ i, lower_semicontinuous_at_supr $ λ hi, h i hi

lemma lower_semicontinuous_on_supr {f : ι → α → δ}
  (h : ∀ i, lower_semicontinuous_on (f i) s) :
  lower_semicontinuous_on (λ x', ⨆ i, f i x') s :=
λ x hx, lower_semicontinuous_within_at_supr (λ i, h i x hx)

lemma lower_semicontinuous_on_bsupr {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, lower_semicontinuous_on (f i hi) s) :
  lower_semicontinuous_on (λ x', ⨆ i hi, f i hi x') s :=
lower_semicontinuous_on_supr $ λ i, lower_semicontinuous_on_supr $ λ hi, h i hi

lemma lower_semicontinuous_supr {f : ι → α → δ}
  (h : ∀ i, lower_semicontinuous (f i)) :
  lower_semicontinuous (λ x', ⨆ i, f i x') :=
λ x, lower_semicontinuous_at_supr (λ i, h i x)

lemma lower_semicontinuous_bsupr {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, lower_semicontinuous (f i hi)) :
  lower_semicontinuous (λ x', ⨆ i hi, f i hi x') :=
lower_semicontinuous_supr $ λ i, lower_semicontinuous_supr $ λ hi, h i hi

end

/-! #### Infinite sums -/

section
variables {ι : Type*}

lemma lower_semicontinuous_within_at_tsum {f : ι → α → ℝ≥0∞}
  (h : ∀ i, lower_semicontinuous_within_at (f i) s x) :
  lower_semicontinuous_within_at (λ x', ∑' i, f i x') s x :=
begin
  simp_rw ennreal.tsum_eq_supr_sum,
  apply lower_semicontinuous_within_at_supr (λ b, _),
  exact lower_semicontinuous_within_at_sum (λ i hi, h i),
end

lemma lower_semicontinuous_at_tsum {f : ι → α → ℝ≥0∞}
  (h : ∀ i, lower_semicontinuous_at (f i) x) :
  lower_semicontinuous_at (λ x', ∑' i, f i x') x :=
begin
  simp_rw [← lower_semicontinuous_within_at_univ_iff] at *,
  exact lower_semicontinuous_within_at_tsum h
end

lemma lower_semicontinuous_on_tsum {f : ι → α → ℝ≥0∞}
  (h : ∀ i, lower_semicontinuous_on (f i) s) :
  lower_semicontinuous_on (λ x', ∑' i, f i x') s :=
λ x hx, lower_semicontinuous_within_at_tsum (λ i, h i x hx)

lemma lower_semicontinuous_tsum {f : ι → α → ℝ≥0∞}
  (h : ∀ i, lower_semicontinuous (f i)) :
  lower_semicontinuous (λ x', ∑' i, f i x') :=
λ x, lower_semicontinuous_at_tsum (λ i, h i x)

end

/-!
### Upper semicontinuous functions
-/

/-! #### Basic dot notation interface for upper semicontinuity -/

lemma upper_semicontinuous_within_at.mono (h : upper_semicontinuous_within_at f s x)
  (hst : t ⊆ s) : upper_semicontinuous_within_at f t x :=
λ y hy, filter.eventually.filter_mono (nhds_within_mono _ hst) (h y hy)

lemma upper_semicontinuous_within_at_univ_iff :
  upper_semicontinuous_within_at f univ x ↔ upper_semicontinuous_at f x :=
by simp [upper_semicontinuous_within_at, upper_semicontinuous_at, nhds_within_univ]

lemma upper_semicontinuous_at.upper_semicontinuous_within_at
  (s : set α) (h : upper_semicontinuous_at f x) : upper_semicontinuous_within_at f s x :=
λ y hy, filter.eventually.filter_mono nhds_within_le_nhds (h y hy)

lemma upper_semicontinuous_on.upper_semicontinuous_within_at
  (h : upper_semicontinuous_on f s) (hx : x ∈ s) :
  upper_semicontinuous_within_at f s x :=
h x hx

lemma upper_semicontinuous_on.mono (h : upper_semicontinuous_on f s) (hst : t ⊆ s) :
  upper_semicontinuous_on f t :=
λ x hx, (h x (hst hx)).mono hst

lemma upper_semicontinuous_on_univ_iff :
  upper_semicontinuous_on f univ ↔ upper_semicontinuous f :=
by simp [upper_semicontinuous_on, upper_semicontinuous, upper_semicontinuous_within_at_univ_iff]

lemma upper_semicontinuous.upper_semicontinuous_at
  (h : upper_semicontinuous f) (x : α) : upper_semicontinuous_at f x :=
h x

lemma upper_semicontinuous.upper_semicontinuous_within_at
  (h : upper_semicontinuous f) (s : set α) (x : α) : upper_semicontinuous_within_at f s x :=
(h x).upper_semicontinuous_within_at s

lemma upper_semicontinuous.upper_semicontinuous_on
  (h : upper_semicontinuous f) (s : set α) : upper_semicontinuous_on f s :=
λ x hx, h.upper_semicontinuous_within_at s x

/-! #### Constants -/

lemma upper_semicontinuous_within_at_const :
  upper_semicontinuous_within_at (λ x, z) s x :=
λ y hy, filter.eventually_of_forall (λ x, hy)

lemma upper_semicontinuous_at_const :
  upper_semicontinuous_at (λ x, z) x :=
λ y hy, filter.eventually_of_forall (λ x, hy)

lemma upper_semicontinuous_on_const :
  upper_semicontinuous_on (λ x, z) s :=
λ x hx, upper_semicontinuous_within_at_const

lemma upper_semicontinuous_const :
  upper_semicontinuous (λ (x : α), z) :=
λ x, upper_semicontinuous_at_const

/-! #### Indicators -/

section
variables [has_zero β]

lemma is_open.upper_semicontinuous_indicator (hs : is_open s) (hy : y ≤ 0) :
  upper_semicontinuous (indicator s (λ x, y)) :=
@is_open.lower_semicontinuous_indicator α _ (order_dual β) _ s y _ hs hy

lemma is_open.upper_semicontinuous_on_indicator (hs : is_open s) (hy : y ≤ 0) :
  upper_semicontinuous_on (indicator s (λ x, y)) t :=
(hs.upper_semicontinuous_indicator hy).upper_semicontinuous_on t

lemma is_open.upper_semicontinuous_at_indicator (hs : is_open s) (hy : y ≤ 0) :
  upper_semicontinuous_at (indicator s (λ x, y)) x :=
(hs.upper_semicontinuous_indicator hy).upper_semicontinuous_at x

lemma is_open.upper_semicontinuous_within_at_indicator (hs : is_open s) (hy : y ≤ 0) :
  upper_semicontinuous_within_at (indicator s (λ x, y)) t x :=
(hs.upper_semicontinuous_indicator hy).upper_semicontinuous_within_at t x

lemma is_closed.upper_semicontinuous_indicator (hs : is_closed s) (hy : 0 ≤ y) :
  upper_semicontinuous (indicator s (λ x, y)) :=
@is_closed.lower_semicontinuous_indicator α _ (order_dual β) _ s y _ hs hy

lemma is_closed.upper_semicontinuous_on_indicator (hs : is_closed s) (hy : 0 ≤ y) :
  upper_semicontinuous_on (indicator s (λ x, y)) t :=
(hs.upper_semicontinuous_indicator hy).upper_semicontinuous_on t

lemma is_closed.upper_semicontinuous_at_indicator (hs : is_closed s) (hy : 0 ≤ y) :
  upper_semicontinuous_at (indicator s (λ x, y)) x :=
(hs.upper_semicontinuous_indicator hy).upper_semicontinuous_at x

lemma is_closed.upper_semicontinuous_within_at_indicator (hs : is_closed s) (hy : 0 ≤ y) :
  upper_semicontinuous_within_at (indicator s (λ x, y)) t x :=
(hs.upper_semicontinuous_indicator hy).upper_semicontinuous_within_at t x

end

/-! #### Relationship with continuity -/

theorem upper_semicontinuous_iff_is_open :
  upper_semicontinuous f ↔ ∀ y, is_open (f ⁻¹' (Iio y)) :=
⟨λ H y, is_open_iff_mem_nhds.2 (λ x hx, H x y hx), λ H x y y_lt, is_open.mem_nhds (H y) y_lt⟩

lemma upper_semicontinuous.is_open_preimage (hf : upper_semicontinuous f) (y : β) :
  is_open (f ⁻¹' (Iio y)) :=
upper_semicontinuous_iff_is_open.1 hf y

section
variables {γ : Type*} [linear_order γ] [topological_space γ] [order_topology γ]

lemma continuous_within_at.upper_semicontinuous_within_at {f : α → γ}
  (h : continuous_within_at f s x) : upper_semicontinuous_within_at f s x :=
λ y hy, h (Iio_mem_nhds hy)

lemma continuous_at.upper_semicontinuous_at {f : α → γ}
  (h : continuous_at f x) : upper_semicontinuous_at f x :=
λ y hy, h (Iio_mem_nhds hy)

lemma continuous_on.upper_semicontinuous_on {f : α → γ}
  (h : continuous_on f s) : upper_semicontinuous_on f s :=
λ x hx, (h x hx).upper_semicontinuous_within_at

lemma continuous.upper_semicontinuous {f : α → γ}
  (h : continuous f) : upper_semicontinuous f :=
λ x, h.continuous_at.upper_semicontinuous_at

end

/-! ### Composition -/

section
variables {γ : Type*} [linear_order γ] [topological_space γ] [order_topology γ]
variables {δ : Type*} [linear_order δ] [topological_space δ] [order_topology δ]

lemma continuous_at.comp_upper_semicontinuous_within_at
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : upper_semicontinuous_within_at f s x)
  (gmon : monotone g) : upper_semicontinuous_within_at (g ∘ f) s x :=
@continuous_at.comp_lower_semicontinuous_within_at α _ x s (order_dual γ) _ _ _
  (order_dual δ) _ _ _ g f hg hf (λ x y hxy, gmon hxy)

lemma continuous_at.comp_upper_semicontinuous_at
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : upper_semicontinuous_at f x)
  (gmon : monotone g) : upper_semicontinuous_at (g ∘ f) x :=
@continuous_at.comp_lower_semicontinuous_at α _ x (order_dual γ) _ _ _
  (order_dual δ) _ _ _ g f hg hf (λ x y hxy, gmon hxy)

lemma continuous.comp_upper_semicontinuous_on
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : upper_semicontinuous_on f s)
  (gmon : monotone g) : upper_semicontinuous_on (g ∘ f) s :=
λ x hx, (hg.continuous_at).comp_upper_semicontinuous_within_at (hf x hx) gmon

lemma continuous.comp_upper_semicontinuous
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : upper_semicontinuous f)
  (gmon : monotone g) : upper_semicontinuous (g ∘ f) :=
λ x, (hg.continuous_at).comp_upper_semicontinuous_at (hf x) gmon

lemma continuous_at.comp_upper_semicontinuous_within_at_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : upper_semicontinuous_within_at f s x)
  (gmon : antitone g) : lower_semicontinuous_within_at (g ∘ f) s x :=
@continuous_at.comp_upper_semicontinuous_within_at α _ x s γ _ _ _ (order_dual δ) _ _ _
  g f hg hf gmon

lemma continuous_at.comp_upper_semicontinuous_at_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous_at g (f x)) (hf : upper_semicontinuous_at f x)
  (gmon : antitone g) : lower_semicontinuous_at (g ∘ f) x :=
@continuous_at.comp_upper_semicontinuous_at α _ x γ _ _ _ (order_dual δ) _ _ _ g f hg hf gmon

lemma continuous.comp_upper_semicontinuous_on_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : upper_semicontinuous_on f s)
  (gmon : antitone g) : lower_semicontinuous_on (g ∘ f) s :=
λ x hx, (hg.continuous_at).comp_upper_semicontinuous_within_at_antitone (hf x hx) gmon

lemma continuous.comp_upper_semicontinuous_antitone
  {g : γ → δ} {f : α → γ} (hg : continuous g) (hf : upper_semicontinuous f)
  (gmon : antitone g) : lower_semicontinuous (g ∘ f) :=
λ x, (hg.continuous_at).comp_upper_semicontinuous_at_antitone (hf x) gmon

end

/-! #### Addition -/

section
variables {ι : Type*} {γ : Type*} [linear_ordered_add_comm_monoid γ]
[topological_space γ] [order_topology γ]

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma upper_semicontinuous_within_at.add' {f g : α → γ}
  (hf : upper_semicontinuous_within_at f s x) (hg : upper_semicontinuous_within_at g s x)
  (hcont : continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  upper_semicontinuous_within_at (λ z, f z + g z) s x :=
@lower_semicontinuous_within_at.add' α _ x s (order_dual γ) _ _ _ _ _ hf hg hcont

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma upper_semicontinuous_at.add' {f g : α → γ}
  (hf : upper_semicontinuous_at f x) (hg : upper_semicontinuous_at g x)
  (hcont : continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  upper_semicontinuous_at (λ z, f z + g z) x :=
by { simp_rw [← upper_semicontinuous_within_at_univ_iff] at *, exact hf.add' hg hcont }

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma upper_semicontinuous_on.add' {f g : α → γ}
  (hf : upper_semicontinuous_on f s) (hg : upper_semicontinuous_on g s)
  (hcont : ∀ x ∈ s, continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  upper_semicontinuous_on (λ z, f z + g z) s :=
λ x hx, (hf x hx).add' (hg x hx) (hcont x hx)

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with an
explicit continuity assumption on addition, for application to `ereal`. The unprimed version of
the lemma uses `[has_continuous_add]`. -/
lemma upper_semicontinuous.add' {f g : α → γ}
  (hf : upper_semicontinuous f) (hg : upper_semicontinuous g)
  (hcont : ∀ x, continuous_at (λ (p : γ × γ), p.1 + p.2) (f x, g x)) :
  upper_semicontinuous (λ z, f z + g z) :=
λ x, (hf x).add' (hg x) (hcont x)

variable [has_continuous_add γ]

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma upper_semicontinuous_within_at.add {f g : α → γ}
  (hf : upper_semicontinuous_within_at f s x) (hg : upper_semicontinuous_within_at g s x) :
  upper_semicontinuous_within_at (λ z, f z + g z) s x :=
hf.add' hg continuous_add.continuous_at

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma upper_semicontinuous_at.add {f g : α → γ}
  (hf : upper_semicontinuous_at f x) (hg : upper_semicontinuous_at g x) :
  upper_semicontinuous_at (λ z, f z + g z) x :=
hf.add' hg continuous_add.continuous_at

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma upper_semicontinuous_on.add {f g : α → γ}
  (hf : upper_semicontinuous_on f s) (hg : upper_semicontinuous_on g s) :
  upper_semicontinuous_on (λ z, f z + g z) s :=
hf.add' hg (λ x hx, continuous_add.continuous_at)

/-- The sum of two upper semicontinuous functions is upper semicontinuous. Formulated with
`[has_continuous_add]`. The primed version of the lemma uses an explicit continuity assumption on
addition, for application to `ereal`. -/
lemma upper_semicontinuous.add {f g : α → γ}
  (hf : upper_semicontinuous f) (hg : upper_semicontinuous g) :
  upper_semicontinuous (λ z, f z + g z) :=
hf.add' hg (λ x, continuous_add.continuous_at)

lemma upper_semicontinuous_within_at_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, upper_semicontinuous_within_at (f i) s x) :
  upper_semicontinuous_within_at (λ z, (∑ i in a, f i z)) s x :=
@lower_semicontinuous_within_at_sum α _ x s ι (order_dual γ) _ _ _ _ f a ha

lemma upper_semicontinuous_at_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, upper_semicontinuous_at (f i) x) :
  upper_semicontinuous_at (λ z, (∑ i in a, f i z)) x :=
begin
  simp_rw [← upper_semicontinuous_within_at_univ_iff] at *,
  exact upper_semicontinuous_within_at_sum ha
end

lemma upper_semicontinuous_on_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, upper_semicontinuous_on (f i) s) :
  upper_semicontinuous_on (λ z, (∑ i in a, f i z)) s :=
λ x hx, upper_semicontinuous_within_at_sum (λ i hi, ha i hi x hx)

lemma upper_semicontinuous_sum {f : ι → α → γ} {a : finset ι}
  (ha : ∀ i ∈ a, upper_semicontinuous (f i)) :
  upper_semicontinuous (λ z, (∑ i in a, f i z)) :=
λ x, upper_semicontinuous_at_sum (λ i hi, ha i hi x)

end

/-! #### Infimum -/

section
variables {ι : Sort*} {δ : Type*} [complete_linear_order δ]

lemma upper_semicontinuous_within_at_infi {f : ι → α → δ}
  (h : ∀ i, upper_semicontinuous_within_at (f i) s x) :
  upper_semicontinuous_within_at (λ x', ⨅ i, f i x') s x :=
@lower_semicontinuous_within_at_supr α _ x s ι (order_dual δ) _ f h

lemma upper_semicontinuous_within_at_binfi {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, upper_semicontinuous_within_at (f i hi) s x) :
  upper_semicontinuous_within_at (λ x', ⨅ i hi, f i hi x') s x :=
upper_semicontinuous_within_at_infi $ λ i, upper_semicontinuous_within_at_infi $ λ hi, h i hi

lemma upper_semicontinuous_at_infi {f : ι → α → δ}
  (h : ∀ i, upper_semicontinuous_at (f i) x) :
  upper_semicontinuous_at (λ x', ⨅ i, f i x') x :=
@lower_semicontinuous_at_supr α _ x ι (order_dual δ) _ f h

lemma upper_semicontinuous_at_binfi {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, upper_semicontinuous_at (f i hi) x) :
  upper_semicontinuous_at (λ x', ⨅ i hi, f i hi x') x :=
upper_semicontinuous_at_infi $ λ i, upper_semicontinuous_at_infi $ λ hi, h i hi

lemma upper_semicontinuous_on_infi {f : ι → α → δ}
  (h : ∀ i, upper_semicontinuous_on (f i) s) :
  upper_semicontinuous_on (λ x', ⨅ i, f i x') s :=
λ x hx, upper_semicontinuous_within_at_infi (λ i, h i x hx)

lemma upper_semicontinuous_on_binfi {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, upper_semicontinuous_on (f i hi) s) :
  upper_semicontinuous_on (λ x', ⨅ i hi, f i hi x') s :=
upper_semicontinuous_on_infi $ λ i, upper_semicontinuous_on_infi $ λ hi, h i hi

lemma upper_semicontinuous_infi {f : ι → α → δ}
  (h : ∀ i, upper_semicontinuous (f i)) :
  upper_semicontinuous (λ x', ⨅ i, f i x') :=
λ x, upper_semicontinuous_at_infi (λ i, h i x)

lemma upper_semicontinuous_binfi {p : ι → Prop} {f : Π i (h : p i), α → δ}
  (h : ∀ i hi, upper_semicontinuous (f i hi)) :
  upper_semicontinuous (λ x', ⨅ i hi, f i hi x') :=
upper_semicontinuous_infi $ λ i, upper_semicontinuous_infi $ λ hi, h i hi

end

section
variables {γ : Type*} [linear_order γ] [topological_space γ] [order_topology γ]

lemma continuous_within_at_iff_lower_upper_semicontinuous_within_at {f : α → γ} :
  continuous_within_at f s x ↔
    lower_semicontinuous_within_at f s x ∧ upper_semicontinuous_within_at f s x:=
begin
  refine ⟨λ h, ⟨h.lower_semicontinuous_within_at, h.upper_semicontinuous_within_at⟩, _⟩,
  rintros ⟨h₁, h₂⟩,
  assume v hv,
  simp only [filter.mem_map],
  by_cases Hl : ∃ l, l < f x,
  { rcases exists_Ioc_subset_of_mem_nhds hv Hl with ⟨l, lfx, hl⟩,
    by_cases Hu : ∃ u, f x < u,
    { rcases exists_Ico_subset_of_mem_nhds hv Hu with ⟨u, fxu, hu⟩,
      filter_upwards [h₁ l lfx, h₂ u fxu],
      assume a lfa fau,
      cases le_or_gt (f a) (f x) with h h,
      { exact hl ⟨lfa, h⟩ },
      { exact hu ⟨le_of_lt h, fau⟩ } },
    { simp only [not_exists, not_lt] at Hu,
      filter_upwards [h₁ l lfx],
      assume a lfa,
      exact hl ⟨lfa, Hu (f a)⟩ } },
  { simp only [not_exists, not_lt] at Hl,
    by_cases Hu : ∃ u, f x < u,
    { rcases exists_Ico_subset_of_mem_nhds hv Hu with ⟨u, fxu, hu⟩,
      filter_upwards [h₂ u fxu],
      assume a lfa,
      apply hu,
      exact ⟨Hl (f a), lfa⟩ },
    { simp only [not_exists, not_lt] at Hu,
      apply filter.eventually_of_forall,
      assume a,
      have : f a = f x := le_antisymm (Hu _) (Hl _),
      rw this,
      exact mem_of_mem_nhds hv } }
end

lemma continuous_at_iff_lower_upper_semicontinuous_at {f : α → γ} :
  continuous_at f x ↔ (lower_semicontinuous_at f x ∧ upper_semicontinuous_at f x) :=
by simp_rw [← continuous_within_at_univ, ← lower_semicontinuous_within_at_univ_iff,
  ← upper_semicontinuous_within_at_univ_iff,
  continuous_within_at_iff_lower_upper_semicontinuous_within_at]

lemma continuous_on_iff_lower_upper_semicontinuous_on {f : α → γ} :
  continuous_on f s ↔ (lower_semicontinuous_on f s ∧ upper_semicontinuous_on f s) :=
begin
  simp only [continuous_on, continuous_within_at_iff_lower_upper_semicontinuous_within_at],
  exact ⟨λ H, ⟨λ x hx, (H x hx).1, λ x hx, (H x hx).2⟩, λ H x hx, ⟨H.1 x hx, H.2 x hx⟩⟩
end

lemma continuous_iff_lower_upper_semicontinuous {f : α → γ} :
  continuous f ↔ (lower_semicontinuous f ∧ upper_semicontinuous f) :=
by simp_rw [continuous_iff_continuous_on_univ, continuous_on_iff_lower_upper_semicontinuous_on,
    lower_semicontinuous_on_univ_iff, upper_semicontinuous_on_univ_iff]

end


import data.pnat.basic

/-! # IMO 2007 A2 -/

namespace IMOSL
namespace IMO2007A2

/-! ## `nat` version -/
def good' (f : ℕ → ℕ) := ∀ m n : ℕ, f m + f (f n) ≤ f (m + n + 1)

/-! ## `pnat` version -/
def good (f : ℕ+ → ℕ+) := ∀ m n : ℕ+, f m + f (f n) ≤ f (m + n) + 1

/-! ## Correspondence between `good` and `good'` -/
section correspondence

private lemma pnat_to_nat_prop {P : ℕ+ → Prop} : (∀ n : ℕ+, P n) ↔ (∀ n : ℕ, P n.succ_pnat) :=
  ⟨λ h n, h n.succ_pnat, λ h n, by rw ← pnat.succ_pnat_nat_pred n; exact h _⟩

private lemma pnat_to_nat_prop2 {P : ℕ+ → ℕ+ → Prop} :
  (∀ m n : ℕ+, P m n) ↔ (∀ m n : ℕ, P m.succ_pnat n.succ_pnat) :=
  by rw pnat_to_nat_prop; refine forall_congr (λ m, _); rw pnat_to_nat_prop

private lemma succ_pnat_add_succ_pnat (m n : ℕ) :
  m.succ_pnat + n.succ_pnat = (m + n).succ_pnat + 1 :=
  by rw ← pnat.coe_inj; simp_rw [pnat.add_coe, nat.succ_pnat_coe];
    rw [nat.add_succ, nat.succ_add, positive.coe_one]

private lemma good_iff_good' (f : ℕ+ → ℕ+) : good f ↔ good' (λ x, (f x.succ_pnat).nat_pred) :=
begin
  obtain ⟨g, rfl⟩ : ∃ g : ℕ → ℕ, f = λ x, (g x.nat_pred).succ_pnat :=
    ⟨λ x, (f x.succ_pnat).nat_pred,
      funext (λ x, by rw [pnat.succ_pnat_nat_pred, pnat.succ_pnat_nat_pred])⟩,
  rw [good, pnat_to_nat_prop2],
  refine forall₂_congr (λ m n, _),
  rw [nat.nat_pred_succ_pnat, succ_pnat_add_succ_pnat, succ_pnat_add_succ_pnat,
      add_le_add_iff_right, nat.succ_pnat_le_succ_pnat],
  refl
end

private lemma correspondence (N k : ℕ+) :
  (∃ f : ℕ+ → ℕ+, good f ∧ f N = k) ↔ (∃ f : ℕ → ℕ, good' f ∧ f N.nat_pred = k.nat_pred) :=
begin
  refine ⟨λ h, _, λ h, _⟩,
  { rcases h with ⟨f, h, rfl⟩,
    rw good_iff_good' at h,
    refine ⟨_, h, _⟩,
    rw pnat.succ_pnat_nat_pred },
  { rcases h with ⟨f, h, h0⟩,
    replace h : good (λ x, (f x.nat_pred).succ_pnat) := by rw good_iff_good'; exact h,
    refine ⟨_, h, _⟩,
    rw [h0, pnat.succ_pnat_nat_pred] }
end

end correspondence

/-! ## Main results -/

private lemma good'_monotone {f : ℕ → ℕ} (h : good' f) : monotone f :=
begin
  intros x y h0,
  rw le_iff_exists_add at h0,
  rcases h0 with ⟨c, rfl⟩,
  cases c with c c,
  rw add_zero,
  rw [nat.succ_eq_add_one, ← add_assoc],
  exact le_trans le_self_add (h x c)
end

private lemma good'_val_bound {f : ℕ → ℕ} (h : good' f) (N : ℕ) : f N ≤ N + 1 :=
begin
  generalize hK : N + 1 = K,
  rw [← not_lt, ← nat.add_one_le_iff]; intros h0,

  -- We prove that `f(mK) ≥ mf(K)` for all `m : ℕ`.
  have h1 : ∀ m : ℕ, m * f K ≤ f (m * K) :=
  begin
    intros m; induction m with m m_ih,
    rw [zero_mul, zero_mul]; exact zero_le _,
    rw [nat.succ_mul, nat.succ_mul, ← hK, ← add_assoc, hK],
    exact le_trans (add_le_add m_ih (good'_monotone h (nat.le_of_succ_le h0))) (h _ _)
  end,

  -- For the case `m = K`, get some `d ≥ N` such that `f(K^2) = K^2 + d + 1`
  replace h1 : ∃ d : ℕ, N ≤ d ∧ f (K * K) = K * K + d + 1 :=
  begin
    replace h0 := le_trans h0 (good'_monotone h N.le_succ),
    rw [nat.succ_eq_add_one, hK] at h0,
    replace h1 := le_trans (nat.mul_le_mul_left _ h0) (h1 K),
    rw [mul_add_one, le_iff_exists_add] at h1,
    cases h1 with c h1,
    refine ⟨N + c, le_self_add, _⟩,
    rw [add_assoc, add_right_comm, hK, ← add_assoc, ← h1]
  end,

  -- Finishing
  rcases h1 with ⟨d, h2, h3⟩,
  replace hK := h d (K * K),
  rw [add_comm d, h3, add_le_iff_nonpos_left, nonpos_iff_eq_zero] at hK,
  revert hK; exact ne_of_gt (lt_of_lt_of_le K.succ_pos (le_trans h0 (good'_monotone h h2)))
end

private lemma sub_right_good' (c : ℕ) : good' (λ n, n - c) :=
begin
  intros m n; dsimp only [],
  cases le_total n c with h h,
  rw [nat.sub_eq_zero_of_le h, nat.zero_sub, add_zero, add_assoc],
  exact nat.sub_le_sub_right le_self_add _,
  rw le_iff_exists_add at h; rcases h with ⟨d, rfl⟩,
  rw [nat.add_sub_cancel_left, add_right_comm, add_comm c, ← add_assoc, nat.add_sub_cancel],
  exact add_le_add (le_trans (nat.sub_le m c) m.le_succ) (nat.sub_le d c)
end

private lemma add_bin_div_good' {K : ℕ} (h : 1 < K) : good' (λ n, n + ite (K ∣ n + 1) 1 0) :=
begin
  intros m n; dsimp only [],
  rw [add_add_add_comm, ← add_assoc m, add_assoc, add_assoc (m + n), add_le_add_iff_left],
  by_cases h0 : K ∣ n + 1,
  { simp_rw [if_pos h0, add_assoc m, add_left_comm m, nat.dvd_add_right h0],
    rw [← add_assoc, add_le_iff_nonpos_right, nonpos_iff_eq_zero, ite_eq_right_iff],
    intros h1; exfalso; exact ne_of_gt h (nat.eq_one_of_dvd_one h1) },
  { rw [if_neg h0, zero_add, add_zero, if_neg h0, add_zero],
    exact le_trans (ite_le_sup 1 0 _) le_self_add }
end

/-- Final solution, `nat` version, `N = 0` -/
theorem final_solution_nat_zero (k : ℕ) : (∃ f : ℕ → ℕ, good' f ∧ f 0 = k) ↔ k = 0 :=
begin
  refine ⟨λ h, _, λ h, ⟨λ _, 0, λ m n, by rw add_zero, h.symm⟩⟩,
  rcases h with ⟨f, h, rfl⟩,
  by_contra h0; rw [← ne.def, ← pos_iff_ne_zero] at h0,
  exact not_le_of_lt (lt_of_lt_of_le (lt_add_of_pos_left _ h0) (h 0 0)) (good'_monotone h h0)
end

/-- Final solution, `nat` version, `N > 0` -/
theorem final_solution_nat_ne_zero {N : ℕ} (h : 0 < N) (k : ℕ) :
  (∃ f : ℕ → ℕ, good' f ∧ f N = k) ↔ k ≤ N + 1 :=
begin
  refine ⟨λ h0, _, λ h0, _⟩,
  rcases h0 with ⟨f, h0, rfl⟩,
  exact good'_val_bound h0 N,
  rw [le_iff_lt_or_eq, nat.lt_succ_iff, le_iff_exists_add] at h0,
  rcases h0 with ⟨c, rfl⟩ | rfl,
  exact ⟨λ n, n - c, sub_right_good' c, nat.add_sub_cancel k c⟩,
  exact ⟨_, add_bin_div_good' (nat.succ_lt_succ h), by rw if_pos (dvd_refl _)⟩
end

/-- Final solution, `pnat` version, `N = 1` -/
theorem final_solution_pnat_one (k : ℕ+) :
  (∃ f : ℕ+ → ℕ+, good f ∧ f 1 = k) ↔ k = 1 :=
begin
  rw correspondence; change (1 : ℕ+).nat_pred with 0,
  rw final_solution_nat_zero; change 0 with (1 : ℕ+).nat_pred,
  rw pnat.nat_pred_inj
end

/-- Final solution, `pnat` version, `N > 1` -/
theorem final_solution_pnat_ne_one {N : ℕ+} (h : 1 < N) (k : ℕ+) :
  (∃ f : ℕ+ → ℕ+, good f ∧ f N = k) ↔ k ≤ N + 1 :=
begin
  rw [correspondence, final_solution_nat_ne_zero, pnat.nat_pred_add_one, pnat.nat_pred,
      tsub_le_iff_right, ← pnat.one_coe, ← pnat.add_coe, pnat.coe_le_coe],
  change 0 with (1 : ℕ+).nat_pred,
  rwa pnat.nat_pred_lt_nat_pred
end

end IMO2007A2
end IMOSL


/-
Copyright (c) 2020 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen

Notation for vectors and matrices
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.fintype.card
import Mathlib.data.matrix.basic
import Mathlib.tactic.fin_cases
import Mathlib.PostPort

universes u u_1 u_2 u_3

namespace Mathlib

/-!
# Matrix and vector notation

This file defines notation for vectors and matrices. Given `a b c d : α`,
the notation allows us to write `![a, b, c, d] : fin 4 → α`.
Nesting vectors gives a matrix, so `![![a, b], ![c, d]] : matrix (fin 2) (fin 2) α`.
This file includes `simp` lemmas for applying operations in
`data.matrix.basic` to values built out of this notation.

## Main definitions

* `vec_empty` is the empty vector (or `0` by `n` matrix) `![]`
* `vec_cons` prepends an entry to a vector, so `![a, b]` is `vec_cons a (vec_cons b vec_empty)`

## Implementation notes

The `simp` lemmas require that one of the arguments is of the form `vec_cons _ _`.
This ensures `simp` works with entries only when (some) entries are already given.
In other words, this notation will only appear in the output of `simp` if it
already appears in the input.

## Notations

The main new notation is `![a, b]`, which gets expanded to `vec_cons a (vec_cons b vec_empty)`.
-/

namespace matrix

/-- `![]` is the vector with no entries. -/
def vec_empty {α : Type u} : fin 0 → α := fin_zero_elim

/-- `vec_cons h t` prepends an entry `h` to a vector `t`.

The inverse functions are `vec_head` and `vec_tail`.
The notation `![a, b, ...]` expands to `vec_cons a (vec_cons b ...)`.
-/
def vec_cons {α : Type u} {n : ℕ} (h : α) (t : fin n → α) : fin (Nat.succ n) → α := fin.cons h t

/-- `vec_head v` gives the first entry of the vector `v` -/
def vec_head {α : Type u} {n : ℕ} (v : fin (Nat.succ n) → α) : α := v 0

/-- `vec_tail v` gives a vector consisting of all entries of `v` except the first -/
def vec_tail {α : Type u} {n : ℕ} (v : fin (Nat.succ n) → α) : fin n → α := v ∘ fin.succ

theorem empty_eq {α : Type u} (v : fin 0 → α) : v = vec_empty :=
  funext
    fun (i : fin 0) =>
      false.dcases_on (fun (h : i ∈ fintype.elems (fin 0)) => v i = vec_empty i)
        (fintype.complete i)

@[simp] theorem head_fin_const {α : Type u} {n : ℕ} (a : α) :
    (vec_head fun (i : fin (n + 1)) => a) = a :=
  rfl

@[simp] theorem cons_val_zero {α : Type u} {m : ℕ} (x : α) (u : fin m → α) : vec_cons x u 0 = x :=
  rfl

theorem cons_val_zero' {α : Type u} {m : ℕ} (h : 0 < Nat.succ m) (x : α) (u : fin m → α) :
    vec_cons x u { val := 0, property := h } = x :=
  rfl

@[simp] theorem cons_val_succ {α : Type u} {m : ℕ} (x : α) (u : fin m → α) (i : fin m) :
    vec_cons x u (fin.succ i) = u i :=
  sorry

@[simp] theorem cons_val_succ' {α : Type u} {m : ℕ} {i : ℕ} (h : Nat.succ i < Nat.succ m) (x : α)
    (u : fin m → α) :
    vec_cons x u { val := Nat.succ i, property := h } =
        u { val := i, property := nat.lt_of_succ_lt_succ h } :=
  sorry

@[simp] theorem head_cons {α : Type u} {m : ℕ} (x : α) (u : fin m → α) :
    vec_head (vec_cons x u) = x :=
  rfl

@[simp] theorem tail_cons {α : Type u} {m : ℕ} (x : α) (u : fin m → α) :
    vec_tail (vec_cons x u) = u :=
  sorry

@[simp] theorem empty_val' {α : Type u} {n' : Type u_1} (j : n') :
    (fun (i : fin 0) => vec_empty i j) = vec_empty :=
  empty_eq fun (i : fin 0) => vec_empty i j

@[simp] theorem cons_val' {α : Type u} {m : ℕ} {n' : Type u_2} [fintype n'] (v : n' → α)
    (B : matrix (fin m) n' α) (i : fin (Nat.succ m)) (j : n') :
    vec_cons v B i j = vec_cons (v j) (fun (i : fin m) => B i j) i :=
  sorry

@[simp] theorem head_val' {α : Type u} {m : ℕ} {n' : Type u_2} [fintype n']
    (B : matrix (fin (Nat.succ m)) n' α) (j : n') :
    (vec_head fun (i : fin (Nat.succ m)) => B i j) = vec_head B j :=
  rfl

@[simp] theorem tail_val' {α : Type u} {m : ℕ} {n' : Type u_2} [fintype n']
    (B : matrix (fin (Nat.succ m)) n' α) (j : n') :
    (vec_tail fun (i : fin (Nat.succ m)) => B i j) = fun (i : fin m) => vec_tail B i j :=
  sorry

@[simp] theorem cons_head_tail {α : Type u} {m : ℕ} (u : fin (Nat.succ m) → α) :
    vec_cons (vec_head u) (vec_tail u) = u :=
  fin.cons_self_tail u

@[simp] theorem range_cons {α : Type u} {n : ℕ} (x : α) (u : fin n → α) :
    set.range (vec_cons x u) = singleton x ∪ set.range u :=
  sorry

@[simp] theorem range_empty {α : Type u} (u : fin 0 → α) : set.range u = ∅ := sorry

/-- `![a, b, ...] 1` is equal to `b`.

  The simplifier needs a special lemma for length `≥ 2`, in addition to
  `cons_val_succ`, because `1 : fin 1 = 0 : fin 1`.
-/
@[simp] theorem cons_val_one {α : Type u} {m : ℕ} (x : α) (u : fin (Nat.succ m) → α) :
    vec_cons x u 1 = vec_head u :=
  cons_val_succ x u 0

@[simp] theorem cons_val_fin_one {α : Type u} (x : α) (u : fin 0 → α) (i : fin 1) :
    vec_cons x u i = x :=
  sorry

/-! ### Numeral (`bit0` and `bit1`) indices
The following definitions and `simp` lemmas are to allow any
numeral-indexed element of a vector given with matrix notation to
be extracted by `simp` (even when the numeral is larger than the
number of elements in the vector, which is taken modulo that number
of elements by virtue of the semantics of `bit0` and `bit1` and of
addition on `fin n`).
-/

@[simp] theorem empty_append {α : Type u} {n : ℕ} (v : fin n → α) :
    fin.append (Eq.symm (zero_add n)) vec_empty v = v :=
  sorry

@[simp] theorem cons_append {α : Type u} {m : ℕ} {n : ℕ} {o : ℕ} (ho : o + 1 = m + 1 + n) (x : α)
    (u : fin m → α) (v : fin n → α) :
    fin.append ho (vec_cons x u) v =
        vec_cons x
          (fin.append
            (eq.mp (Eq._oldrec (Eq.refl (o + 1 = m + n + 1)) (propext add_right_cancel_iff))
              (eq.mp (Eq._oldrec (Eq.refl (o + 1 = m + (n + 1))) (Eq.symm (add_assoc m n 1)))
                (eq.mp (Eq._oldrec (Eq.refl (o + 1 = m + (1 + n))) (add_comm 1 n))
                  (eq.mp (Eq._oldrec (Eq.refl (o + 1 = m + 1 + n)) (add_assoc m 1 n)) ho))))
            u v) :=
  sorry

/-- `vec_alt0 v` gives a vector with half the length of `v`, with
only alternate elements (even-numbered). -/
def vec_alt0 {α : Type u} {m : ℕ} {n : ℕ} (hm : m = n + n) (v : fin m → α) (k : fin n) : α :=
  v { val := ↑k + ↑k, property := sorry }

/-- `vec_alt1 v` gives a vector with half the length of `v`, with
only alternate elements (odd-numbered). -/
def vec_alt1 {α : Type u} {m : ℕ} {n : ℕ} (hm : m = n + n) (v : fin m → α) (k : fin n) : α :=
  v { val := ↑k + ↑k + 1, property := sorry }

theorem vec_alt0_append {α : Type u} {n : ℕ} (v : fin n → α) :
    vec_alt0 rfl (fin.append rfl v v) = v ∘ bit0 :=
  sorry

theorem vec_alt1_append {α : Type u} {n : ℕ} (v : fin (n + 1) → α) :
    vec_alt1 rfl (fin.append rfl v v) = v ∘ bit1 :=
  sorry

@[simp] theorem vec_head_vec_alt0 {α : Type u} {m : ℕ} {n : ℕ} (hm : m + bit0 1 = n + 1 + (n + 1))
    (v : fin (m + bit0 1) → α) : vec_head (vec_alt0 hm v) = v 0 :=
  rfl

@[simp] theorem vec_head_vec_alt1 {α : Type u} {m : ℕ} {n : ℕ} (hm : m + bit0 1 = n + 1 + (n + 1))
    (v : fin (m + bit0 1) → α) : vec_head (vec_alt1 hm v) = v 1 :=
  rfl

@[simp] theorem cons_vec_bit0_eq_alt0 {α : Type u} {n : ℕ} (x : α) (u : fin n → α)
    (i : fin (n + 1)) :
    vec_cons x u (bit0 i) = vec_alt0 rfl (fin.append rfl (vec_cons x u) (vec_cons x u)) i :=
  sorry

@[simp] theorem cons_vec_bit1_eq_alt1 {α : Type u} {n : ℕ} (x : α) (u : fin n → α)
    (i : fin (n + 1)) :
    vec_cons x u (bit1 i) = vec_alt1 rfl (fin.append rfl (vec_cons x u) (vec_cons x u)) i :=
  sorry

@[simp] theorem cons_vec_alt0 {α : Type u} {m : ℕ} {n : ℕ} (h : m + 1 + 1 = n + 1 + (n + 1)) (x : α)
    (y : α) (u : fin m → α) :
    vec_alt0 h (vec_cons x (vec_cons y u)) =
        vec_cons x
          (vec_alt0
            (eq.mp (Eq._oldrec (Eq.refl (m + 1 = n + n + 1)) (propext add_right_cancel_iff))
              (eq.mp
                (Eq._oldrec (Eq.refl (m + 1 + 1 = n + n + 1 + 1)) (propext add_right_cancel_iff))
                (eq.mp
                  (Eq._oldrec (Eq.refl (m + 1 + 1 = n + (n + 1) + 1)) (Eq.symm (add_assoc n n 1)))
                  (eq.mp
                    (Eq._oldrec (Eq.refl (m + 1 + 1 = n + (n + 1 + 1)))
                      (Eq.symm (add_assoc n (n + 1) 1)))
                    (eq.mp
                      (Eq._oldrec (Eq.refl (m + 1 + 1 = n + (1 + (n + 1)))) (add_comm 1 (n + 1)))
                      (eq.mp
                        (Eq._oldrec (Eq.refl (m + 1 + 1 = n + 1 + (n + 1))) (add_assoc n 1 (n + 1)))
                        h))))))
            u) :=
  sorry

-- Although proved by simp, extracting element 8 of a five-element

-- vector does not work by simp unless this lemma is present.

@[simp] theorem empty_vec_alt0 (α : Type u_1) {h : 0 = 0 + 0} : vec_alt0 h vec_empty = vec_empty :=
  eq.mpr (id (propext (eq_iff_true_of_subsingleton (vec_alt0 h vec_empty) vec_empty))) trivial

@[simp] theorem cons_vec_alt1 {α : Type u} {m : ℕ} {n : ℕ} (h : m + 1 + 1 = n + 1 + (n + 1)) (x : α)
    (y : α) (u : fin m → α) :
    vec_alt1 h (vec_cons x (vec_cons y u)) =
        vec_cons y
          (vec_alt1
            (eq.mp (Eq._oldrec (Eq.refl (m + 1 = n + n + 1)) (propext add_right_cancel_iff))
              (eq.mp
                (Eq._oldrec (Eq.refl (m + 1 + 1 = n + n + 1 + 1)) (propext add_right_cancel_iff))
                (eq.mp
                  (Eq._oldrec (Eq.refl (m + 1 + 1 = n + (n + 1) + 1)) (Eq.symm (add_assoc n n 1)))
                  (eq.mp
                    (Eq._oldrec (Eq.refl (m + 1 + 1 = n + (n + 1 + 1)))
                      (Eq.symm (add_assoc n (n + 1) 1)))
                    (eq.mp
                      (Eq._oldrec (Eq.refl (m + 1 + 1 = n + (1 + (n + 1)))) (add_comm 1 (n + 1)))
                      (eq.mp
                        (Eq._oldrec (Eq.refl (m + 1 + 1 = n + 1 + (n + 1))) (add_assoc n 1 (n + 1)))
                        h))))))
            u) :=
  sorry

-- Although proved by simp, extracting element 9 of a five-element

-- vector does not work by simp unless this lemma is present.

@[simp] theorem empty_vec_alt1 (α : Type u_1) {h : 0 = 0 + 0} : vec_alt1 h vec_empty = vec_empty :=
  eq.mpr (id (propext (eq_iff_true_of_subsingleton (vec_alt1 h vec_empty) vec_empty))) trivial

@[simp] theorem dot_product_empty {α : Type u} [add_comm_monoid α] [Mul α] (v : fin 0 → α)
    (w : fin 0 → α) : dot_product v w = 0 :=
  finset.sum_empty

@[simp] theorem cons_dot_product {α : Type u} {n : ℕ} [add_comm_monoid α] [Mul α] (x : α)
    (v : fin n → α) (w : fin (Nat.succ n) → α) :
    dot_product (vec_cons x v) w = x * vec_head w + dot_product v (vec_tail w) :=
  sorry

@[simp] theorem dot_product_cons {α : Type u} {n : ℕ} [add_comm_monoid α] [Mul α]
    (v : fin (Nat.succ n) → α) (x : α) (w : fin n → α) :
    dot_product v (vec_cons x w) = vec_head v * x + dot_product (vec_tail v) w :=
  sorry

@[simp] theorem col_empty {α : Type u} (v : fin 0 → α) : col v = vec_empty := empty_eq (col v)

@[simp] theorem col_cons {α : Type u} {m : ℕ} (x : α) (u : fin m → α) :
    col (vec_cons x u) = vec_cons (fun (_x : Unit) => x) (col u) :=
  sorry

@[simp] theorem row_empty {α : Type u} : row vec_empty = fun (_x : Unit) => vec_empty :=
  funext fun (x : Unit) => funext fun (x_1 : fin 0) => Eq.refl (row vec_empty x x_1)

@[simp] theorem row_cons {α : Type u} {m : ℕ} (x : α) (u : fin m → α) :
    row (vec_cons x u) = fun (_x : Unit) => vec_cons x u :=
  funext
    fun (x_1 : Unit) => funext fun (x_2 : fin (Nat.succ m)) => Eq.refl (row (vec_cons x u) x_1 x_2)

@[simp] theorem transpose_empty_rows {α : Type u} {m' : Type u_1} [fintype m']
    (A : matrix m' (fin 0) α) : transpose A = vec_empty :=
  empty_eq (transpose A)

@[simp] theorem transpose_empty_cols {α : Type u} {m' : Type u_1} [fintype m'] :
    transpose vec_empty = fun (i : m') => vec_empty :=
  funext fun (i : m') => empty_eq (transpose vec_empty i)

@[simp] theorem cons_transpose {α : Type u} {m : ℕ} {n' : Type u_2} [fintype n'] (v : n' → α)
    (A : matrix (fin m) n' α) :
    transpose (vec_cons v A) = fun (i : n') => vec_cons (v i) (transpose A i) :=
  sorry

@[simp] theorem head_transpose {α : Type u} {n : ℕ} {m' : Type u_1} [fintype m']
    (A : matrix m' (fin (Nat.succ n)) α) : vec_head (transpose A) = vec_head ∘ A :=
  rfl

@[simp] theorem tail_transpose {α : Type u} {n : ℕ} {m' : Type u_1} [fintype m']
    (A : matrix m' (fin (Nat.succ n)) α) : vec_tail (transpose A) = transpose (vec_tail ∘ A) :=
  ext fun (i : fin n) (j : m') => Eq.refl (vec_tail (transpose A) i j)

@[simp] theorem empty_mul {α : Type u} {n' : Type u_2} {o' : Type u_3} [fintype n'] [fintype o']
    [semiring α] (A : matrix (fin 0) n' α) (B : matrix n' o' α) : matrix.mul A B = vec_empty :=
  empty_eq (matrix.mul A B)

@[simp] theorem empty_mul_empty {α : Type u} {m' : Type u_1} {o' : Type u_3} [fintype m']
    [fintype o'] [semiring α] (A : matrix m' (fin 0) α) (B : matrix (fin 0) o' α) :
    matrix.mul A B = 0 :=
  rfl

@[simp] theorem mul_empty {α : Type u} {m' : Type u_1} {n' : Type u_2} [fintype m'] [fintype n']
    [semiring α] (A : matrix m' n' α) (B : matrix n' (fin 0) α) :
    matrix.mul A B = fun (_x : m') => vec_empty :=
  funext fun (_x : m') => empty_eq (matrix.mul A B _x)

theorem mul_val_succ {α : Type u} {m : ℕ} {n' : Type u_2} {o' : Type u_3} [fintype n'] [fintype o']
    [semiring α] (A : matrix (fin (Nat.succ m)) n' α) (B : matrix n' o' α) (i : fin m) (j : o') :
    matrix.mul A B (fin.succ i) j = matrix.mul (vec_tail A) B i j :=
  rfl

@[simp] theorem cons_mul {α : Type u} {m : ℕ} {n' : Type u_2} {o' : Type u_3} [fintype n']
    [fintype o'] [semiring α] (v : n' → α) (A : matrix (fin m) n' α) (B : matrix n' o' α) :
    matrix.mul (vec_cons v A) B = vec_cons (vec_mul v B) (matrix.mul A B) :=
  sorry

@[simp] theorem empty_vec_mul {α : Type u} {o' : Type u_3} [fintype o'] [semiring α] (v : fin 0 → α)
    (B : matrix (fin 0) o' α) : vec_mul v B = 0 :=
  rfl

@[simp] theorem vec_mul_empty {α : Type u} {n' : Type u_2} [fintype n'] [semiring α] (v : n' → α)
    (B : matrix n' (fin 0) α) : vec_mul v B = vec_empty :=
  empty_eq (vec_mul v B)

@[simp] theorem cons_vec_mul {α : Type u} {n : ℕ} {o' : Type u_3} [fintype o'] [semiring α] (x : α)
    (v : fin n → α) (B : matrix (fin (Nat.succ n)) o' α) :
    vec_mul (vec_cons x v) B = x • vec_head B + vec_mul v (vec_tail B) :=
  sorry

@[simp] theorem vec_mul_cons {α : Type u} {n : ℕ} {o' : Type u_3} [fintype o'] [semiring α]
    (v : fin (Nat.succ n) → α) (w : o' → α) (B : matrix (fin n) o' α) :
    vec_mul v (vec_cons w B) = vec_head v • w + vec_mul (vec_tail v) B :=
  sorry

@[simp] theorem empty_mul_vec {α : Type u} {n' : Type u_2} [fintype n'] [semiring α]
    (A : matrix (fin 0) n' α) (v : n' → α) : mul_vec A v = vec_empty :=
  empty_eq (mul_vec A v)

@[simp] theorem mul_vec_empty {α : Type u} {m' : Type u_1} [fintype m'] [semiring α]
    (A : matrix m' (fin 0) α) (v : fin 0 → α) : mul_vec A v = 0 :=
  rfl

@[simp] theorem cons_mul_vec {α : Type u} {m : ℕ} {n' : Type u_2} [fintype n'] [semiring α]
    (v : n' → α) (A : fin m → n' → α) (w : n' → α) :
    mul_vec (vec_cons v A) w = vec_cons (dot_product v w) (mul_vec A w) :=
  sorry

@[simp] theorem mul_vec_cons {n : ℕ} {m' : Type u_1} [fintype m'] {α : Type u_2} [comm_semiring α]
    (A : m' → fin (Nat.succ n) → α) (x : α) (v : fin n → α) :
    mul_vec A (vec_cons x v) = x • vec_head ∘ A + mul_vec (vec_tail ∘ A) v :=
  sorry

@[simp] theorem empty_vec_mul_vec {α : Type u} {n' : Type u_2} [fintype n'] [semiring α]
    (v : fin 0 → α) (w : n' → α) : vec_mul_vec v w = vec_empty :=
  empty_eq (vec_mul_vec v w)

@[simp] theorem vec_mul_vec_empty {α : Type u} {m' : Type u_1} [fintype m'] [semiring α]
    (v : m' → α) (w : fin 0 → α) : vec_mul_vec v w = fun (_x : m') => vec_empty :=
  funext fun (i : m') => empty_eq (vec_mul_vec v w i)

@[simp] theorem cons_vec_mul_vec {α : Type u} {m : ℕ} {n' : Type u_2} [fintype n'] [semiring α]
    (x : α) (v : fin m → α) (w : n' → α) :
    vec_mul_vec (vec_cons x v) w = vec_cons (x • w) (vec_mul_vec v w) :=
  sorry

@[simp] theorem vec_mul_vec_cons {α : Type u} {n : ℕ} {m' : Type u_1} [fintype m'] [semiring α]
    (v : m' → α) (x : α) (w : fin n → α) :
    vec_mul_vec v (vec_cons x w) = fun (i : m') => v i • vec_cons x w :=
  sorry

@[simp] theorem smul_empty {α : Type u} [semiring α] (x : α) (v : fin 0 → α) : x • v = vec_empty :=
  empty_eq (x • v)

@[simp] theorem smul_mat_empty {α : Type u} [semiring α] {m' : Type u_1} (x : α)
    (A : fin 0 → m' → α) : x • A = vec_empty :=
  empty_eq (x • A)

@[simp] theorem smul_cons {α : Type u} {n : ℕ} [semiring α] (x : α) (y : α) (v : fin n → α) :
    x • vec_cons y v = vec_cons (x * y) (x • v) :=
  sorry

@[simp] theorem smul_mat_cons {α : Type u} {m : ℕ} {n' : Type u_2} [fintype n'] [semiring α] (x : α)
    (v : n' → α) (A : matrix (fin m) n' α) : x • vec_cons v A = vec_cons (x • v) (x • A) :=
  sorry

@[simp] theorem empty_add_empty {α : Type u} [Add α] (v : fin 0 → α) (w : fin 0 → α) :
    v + w = vec_empty :=
  empty_eq (v + w)

@[simp] theorem cons_add {α : Type u} {n : ℕ} [Add α] (x : α) (v : fin n → α)
    (w : fin (Nat.succ n) → α) : vec_cons x v + w = vec_cons (x + vec_head w) (v + vec_tail w) :=
  sorry

@[simp] theorem add_cons {α : Type u} {n : ℕ} [Add α] (v : fin (Nat.succ n) → α) (y : α)
    (w : fin n → α) : v + vec_cons y w = vec_cons (vec_head v + y) (vec_tail v + w) :=
  sorry

@[simp] theorem head_add {α : Type u} {n : ℕ} [Add α] (a : fin (Nat.succ n) → α)
    (b : fin (Nat.succ n) → α) : vec_head (a + b) = vec_head a + vec_head b :=
  rfl

@[simp] theorem tail_add {α : Type u} {n : ℕ} [Add α] (a : fin (Nat.succ n) → α)
    (b : fin (Nat.succ n) → α) : vec_tail (a + b) = vec_tail a + vec_tail b :=
  rfl

@[simp] theorem empty_sub_empty {α : Type u} [Sub α] (v : fin 0 → α) (w : fin 0 → α) :
    v - w = vec_empty :=
  empty_eq (v - w)

@[simp] theorem cons_sub {α : Type u} {n : ℕ} [Sub α] (x : α) (v : fin n → α)
    (w : fin (Nat.succ n) → α) : vec_cons x v - w = vec_cons (x - vec_head w) (v - vec_tail w) :=
  sorry

@[simp] theorem sub_cons {α : Type u} {n : ℕ} [Sub α] (v : fin (Nat.succ n) → α) (y : α)
    (w : fin n → α) : v - vec_cons y w = vec_cons (vec_head v - y) (vec_tail v - w) :=
  sorry

@[simp] theorem head_sub {α : Type u} {n : ℕ} [Sub α] (a : fin (Nat.succ n) → α)
    (b : fin (Nat.succ n) → α) : vec_head (a - b) = vec_head a - vec_head b :=
  rfl

@[simp] theorem tail_sub {α : Type u} {n : ℕ} [Sub α] (a : fin (Nat.succ n) → α)
    (b : fin (Nat.succ n) → α) : vec_tail (a - b) = vec_tail a - vec_tail b :=
  rfl

@[simp] theorem zero_empty {α : Type u} [HasZero α] : 0 = vec_empty := empty_eq 0

@[simp] theorem cons_zero_zero {α : Type u} {n : ℕ} [HasZero α] : vec_cons 0 0 = 0 := sorry

@[simp] theorem head_zero {α : Type u} {n : ℕ} [HasZero α] : vec_head 0 = 0 := rfl

@[simp] theorem tail_zero {α : Type u} {n : ℕ} [HasZero α] : vec_tail 0 = 0 := rfl

@[simp] theorem cons_eq_zero_iff {α : Type u} {n : ℕ} [HasZero α] {v : fin n → α} {x : α} :
    vec_cons x v = 0 ↔ x = 0 ∧ v = 0 :=
  sorry

theorem cons_nonzero_iff {α : Type u} {n : ℕ} [HasZero α] {v : fin n → α} {x : α} :
    vec_cons x v ≠ 0 ↔ x ≠ 0 ∨ v ≠ 0 :=
  { mp := fun (h : vec_cons x v ≠ 0) => iff.mp not_and_distrib (h ∘ iff.mpr cons_eq_zero_iff),
    mpr := fun (h : x ≠ 0 ∨ v ≠ 0) => mt (iff.mp cons_eq_zero_iff) (iff.mpr not_and_distrib h) }

@[simp] theorem neg_empty {α : Type u} [Neg α] (v : fin 0 → α) : -v = vec_empty := empty_eq (-v)

@[simp] theorem neg_cons {α : Type u} {n : ℕ} [Neg α] (x : α) (v : fin n → α) :
    -vec_cons x v = vec_cons (-x) (-v) :=
  sorry

@[simp] theorem head_neg {α : Type u} {n : ℕ} [Neg α] (a : fin (Nat.succ n) → α) :
    vec_head (-a) = -vec_head a :=
  rfl

@[simp] theorem tail_neg {α : Type u} {n : ℕ} [Neg α] (a : fin (Nat.succ n) → α) :
    vec_tail (-a) = -vec_tail a :=
  rfl

@[simp] theorem minor_empty {α : Type u} {m' : Type u_1} {n' : Type u_2} {o' : Type u_3}
    [fintype m'] [fintype n'] [fintype o'] (A : matrix m' n' α) (row : fin 0 → m') (col : o' → n') :
    minor A row col = vec_empty :=
  empty_eq (minor A row col)

@[simp] theorem minor_cons_row {α : Type u} {m : ℕ} {m' : Type u_1} {n' : Type u_2} {o' : Type u_3}
    [fintype m'] [fintype n'] [fintype o'] (A : matrix m' n' α) (i : m') (row : fin m → m')
    (col : o' → n') :
    minor A (vec_cons i row) col = vec_cons (fun (j : o') => A i (col j)) (minor A row col) :=
  sorry

end Mathlib