/-
Copyright (c) 2019 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import order.filter.extr
import topology.continuous_on

/-!
# Local extrema of functions on topological spaces

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

## Main definitions

This file defines special versions of `is_*_filter f a l`, `*=min/max/extr`,
from `order/filter/extr` for two kinds of filters: `nhds_within` and `nhds`.
These versions are called `is_local_*_on` and `is_local_*`, respectively.

## Main statements

Many lemmas in this file restate those from `order/filter/extr`, and you can find
a detailed documentation there. These convenience lemmas are provided only to make the dot notation
return propositions of expected types, not just `is_*_filter`.

Here is the list of statements specific to these two types of filters:

* `is_local_*.on`, `is_local_*_on.on_subset`: restrict to a subset;
* `is_local_*_on.inter` : intersect the set with another one;
* `is_*_on.localize` : a global extremum is a local extremum too.
* `is_[local_]*_on.is_local_*` : if we have `is_local_*_on f s a` and `s ∈ 𝓝 a`,
  then we have `is_local_* f a`.

-/

universes u v w x

variables {α : Type u} {β : Type v} {γ : Type w} {δ : Type x} [topological_space α]

open set filter
open_locale topology filter

section preorder

variables [preorder β] [preorder γ] (f : α → β) (s : set α) (a : α)

/-- `is_local_min_on f s a` means that `f a ≤ f x` for all `x ∈ s` in some neighborhood of `a`. -/
def is_local_min_on := is_min_filter f (𝓝[s] a) a

/-- `is_local_max_on f s a` means that `f x ≤ f a` for all `x ∈ s` in some neighborhood of `a`. -/
def is_local_max_on := is_max_filter f (𝓝[s] a) a

/-- `is_local_extr_on f s a` means `is_local_min_on f s a ∨ is_local_max_on f s a`. -/
def is_local_extr_on := is_extr_filter f (𝓝[s] a) a

/-- `is_local_min f a` means that `f a ≤ f x` for all `x` in some neighborhood of `a`. -/
def is_local_min := is_min_filter f (𝓝 a) a

/-- `is_local_max f a` means that `f x ≤ f a` for all `x ∈ s` in some neighborhood of `a`. -/
def is_local_max := is_max_filter f (𝓝 a) a

/-- `is_local_extr_on f s a` means `is_local_min_on f s a ∨ is_local_max_on f s a`. -/
def is_local_extr := is_extr_filter f (𝓝 a) a

variables {f s a}

lemma is_local_extr_on.elim {p : Prop} :
  is_local_extr_on f s a → (is_local_min_on f s a → p) → (is_local_max_on f s a → p) → p :=
or.elim

lemma is_local_extr.elim {p : Prop} :
  is_local_extr f a → (is_local_min f a → p) → (is_local_max f a → p) → p :=
or.elim

/-! ### Restriction to (sub)sets -/

lemma is_local_min.on (h : is_local_min f a) (s) : is_local_min_on f s a :=
h.filter_inf _

lemma is_local_max.on (h : is_local_max f a) (s) : is_local_max_on f s a :=
h.filter_inf _

lemma is_local_extr.on (h : is_local_extr f a) (s) : is_local_extr_on f s a :=
h.filter_inf _

lemma is_local_min_on.on_subset {t : set α} (hf : is_local_min_on f t a) (h : s ⊆ t) :
  is_local_min_on f s a :=
hf.filter_mono $ nhds_within_mono a h

lemma is_local_max_on.on_subset {t : set α} (hf : is_local_max_on f t a) (h : s ⊆ t) :
  is_local_max_on f s a :=
hf.filter_mono $ nhds_within_mono a h

lemma is_local_extr_on.on_subset {t : set α} (hf : is_local_extr_on f t a) (h : s ⊆ t) :
  is_local_extr_on f s a :=
hf.filter_mono $ nhds_within_mono a h

lemma is_local_min_on.inter (hf : is_local_min_on f s a) (t) : is_local_min_on f (s ∩ t) a :=
hf.on_subset (inter_subset_left s t)

lemma is_local_max_on.inter (hf : is_local_max_on f s a) (t) : is_local_max_on f (s ∩ t) a :=
hf.on_subset (inter_subset_left s t)

lemma is_local_extr_on.inter (hf : is_local_extr_on f s a) (t) : is_local_extr_on f (s ∩ t) a :=
hf.on_subset (inter_subset_left s t)

lemma is_min_on.localize (hf : is_min_on f s a) : is_local_min_on f s a :=
hf.filter_mono $ inf_le_right

lemma is_max_on.localize (hf : is_max_on f s a) : is_local_max_on f s a :=
hf.filter_mono $ inf_le_right

lemma is_extr_on.localize (hf : is_extr_on f s a) : is_local_extr_on f s a :=
hf.filter_mono $ inf_le_right

lemma is_local_min_on.is_local_min (hf : is_local_min_on f s a) (hs : s ∈ 𝓝 a) : is_local_min f a :=
have 𝓝 a ≤ 𝓟 s, from le_principal_iff.2 hs,
hf.filter_mono $ le_inf le_rfl this

lemma is_local_max_on.is_local_max (hf : is_local_max_on f s a) (hs : s ∈ 𝓝 a) : is_local_max f a :=
have 𝓝 a ≤ 𝓟 s, from le_principal_iff.2 hs,
hf.filter_mono $ le_inf le_rfl this

lemma is_local_extr_on.is_local_extr (hf : is_local_extr_on f s a) (hs : s ∈ 𝓝 a) :
  is_local_extr f a :=
hf.elim (λ hf, (hf.is_local_min hs).is_extr) (λ hf, (hf.is_local_max hs).is_extr)

lemma is_min_on.is_local_min (hf : is_min_on f s a) (hs : s ∈ 𝓝 a) : is_local_min f a :=
hf.localize.is_local_min hs

lemma is_max_on.is_local_max (hf : is_max_on f s a) (hs : s ∈ 𝓝 a) : is_local_max f a :=
hf.localize.is_local_max hs

lemma is_extr_on.is_local_extr (hf : is_extr_on f s a) (hs : s ∈ 𝓝 a) : is_local_extr f a :=
hf.localize.is_local_extr hs

lemma is_local_min_on.not_nhds_le_map [topological_space β]
  (hf : is_local_min_on f s a) [ne_bot (𝓝[<] (f a))] :
  ¬𝓝 (f a) ≤ map f (𝓝[s] a) :=
λ hle,
have ∀ᶠ y in 𝓝[<] (f a), f a ≤ y,
  from (eventually_map.2 hf).filter_mono (inf_le_left.trans hle),
let ⟨y, hy⟩ := (this.and self_mem_nhds_within).exists in hy.1.not_lt hy.2

lemma is_local_max_on.not_nhds_le_map [topological_space β]
  (hf : is_local_max_on f s a) [ne_bot (𝓝[>] (f a))] :
  ¬𝓝 (f a) ≤ map f (𝓝[s] a) :=
@is_local_min_on.not_nhds_le_map α βᵒᵈ _ _ _ _ _ ‹_› hf ‹_›

lemma is_local_extr_on.not_nhds_le_map [topological_space β]
  (hf : is_local_extr_on f s a) [ne_bot (𝓝[<] (f a))] [ne_bot (𝓝[>] (f a))] :
  ¬𝓝 (f a) ≤ map f (𝓝[s] a) :=
hf.elim (λ h, h.not_nhds_le_map) (λ h, h.not_nhds_le_map)

/-! ### Constant -/

lemma is_local_min_on_const {b : β} : is_local_min_on (λ _, b) s a := is_min_filter_const
lemma is_local_max_on_const {b : β} : is_local_max_on (λ _, b) s a := is_max_filter_const
lemma is_local_extr_on_const {b : β} : is_local_extr_on (λ _, b) s a := is_extr_filter_const

lemma is_local_min_const {b : β} : is_local_min (λ _, b) a := is_min_filter_const
lemma is_local_max_const {b : β} : is_local_max (λ _, b) a := is_max_filter_const
lemma is_local_extr_const {b : β} : is_local_extr (λ _, b) a := is_extr_filter_const

/-! ### Composition with (anti)monotone functions -/

lemma is_local_min.comp_mono (hf : is_local_min f a) {g : β → γ} (hg : monotone g) :
  is_local_min (g ∘ f) a :=
hf.comp_mono hg

lemma is_local_max.comp_mono (hf : is_local_max f a) {g : β → γ} (hg : monotone g) :
  is_local_max (g ∘ f) a :=
hf.comp_mono hg

lemma is_local_extr.comp_mono (hf : is_local_extr f a) {g : β → γ} (hg : monotone g) :
  is_local_extr (g ∘ f) a :=
hf.comp_mono hg

lemma is_local_min.comp_antitone (hf : is_local_min f a) {g : β → γ}
  (hg : antitone g) :
  is_local_max (g ∘ f) a :=
hf.comp_antitone hg

lemma is_local_max.comp_antitone (hf : is_local_max f a) {g : β → γ}
  (hg : antitone g) :
  is_local_min (g ∘ f) a :=
hf.comp_antitone hg

lemma is_local_extr.comp_antitone (hf : is_local_extr f a) {g : β → γ}
  (hg : antitone g) :
  is_local_extr (g ∘ f) a :=
hf.comp_antitone hg

lemma is_local_min_on.comp_mono (hf : is_local_min_on f s a) {g : β → γ} (hg : monotone g) :
  is_local_min_on (g ∘ f) s a :=
hf.comp_mono hg

lemma is_local_max_on.comp_mono (hf : is_local_max_on f s a) {g : β → γ} (hg : monotone g) :
  is_local_max_on (g ∘ f) s a :=
hf.comp_mono hg

lemma is_local_extr_on.comp_mono (hf : is_local_extr_on f s a) {g : β → γ} (hg : monotone g) :
  is_local_extr_on (g ∘ f) s a :=
hf.comp_mono hg

lemma is_local_min_on.comp_antitone (hf : is_local_min_on f s a) {g : β → γ}
  (hg : antitone g) :
  is_local_max_on (g ∘ f) s a :=
hf.comp_antitone hg

lemma is_local_max_on.comp_antitone (hf : is_local_max_on f s a) {g : β → γ}
  (hg : antitone g) :
  is_local_min_on (g ∘ f) s a :=
hf.comp_antitone hg

lemma is_local_extr_on.comp_antitone (hf : is_local_extr_on f s a) {g : β → γ}
  (hg : antitone g) :
  is_local_extr_on (g ∘ f) s a :=
hf.comp_antitone hg

lemma is_local_min.bicomp_mono [preorder δ] {op : β → γ → δ} (hop : ((≤) ⇒ (≤) ⇒ (≤)) op op)
  (hf : is_local_min f a) {g : α → γ} (hg : is_local_min g a) :
  is_local_min (λ x, op (f x) (g x)) a :=
hf.bicomp_mono hop hg

lemma is_local_max.bicomp_mono [preorder δ] {op : β → γ → δ} (hop : ((≤) ⇒ (≤) ⇒ (≤)) op op)
  (hf : is_local_max f a) {g : α → γ} (hg : is_local_max g a) :
  is_local_max (λ x, op (f x) (g x)) a :=
hf.bicomp_mono hop hg

-- No `extr` version because we need `hf` and `hg` to be of the same kind

lemma is_local_min_on.bicomp_mono [preorder δ] {op : β → γ → δ} (hop : ((≤) ⇒ (≤) ⇒ (≤)) op op)
  (hf : is_local_min_on f s a) {g : α → γ} (hg : is_local_min_on g s a) :
  is_local_min_on (λ x, op (f x) (g x)) s a :=
hf.bicomp_mono hop hg

lemma is_local_max_on.bicomp_mono [preorder δ] {op : β → γ → δ} (hop : ((≤) ⇒ (≤) ⇒ (≤)) op op)
  (hf : is_local_max_on f s a) {g : α → γ} (hg : is_local_max_on g s a) :
  is_local_max_on (λ x, op (f x) (g x)) s a :=
hf.bicomp_mono hop hg

/-! ### Composition with `continuous_at` -/

lemma is_local_min.comp_continuous [topological_space δ] {g : δ → α} {b : δ}
  (hf : is_local_min f (g b)) (hg : continuous_at g b) :
  is_local_min (f ∘ g) b :=
hg hf

lemma is_local_max.comp_continuous [topological_space δ] {g : δ → α} {b : δ}
  (hf : is_local_max f (g b)) (hg : continuous_at g b) :
  is_local_max (f ∘ g) b :=
hg hf

lemma is_local_extr.comp_continuous [topological_space δ] {g : δ → α} {b : δ}
  (hf : is_local_extr f (g b)) (hg : continuous_at g b) :
  is_local_extr (f ∘ g) b :=
hf.comp_tendsto hg

lemma is_local_min.comp_continuous_on [topological_space δ] {s : set δ} {g : δ → α} {b : δ}
  (hf : is_local_min f (g b)) (hg : continuous_on g s) (hb : b ∈ s) :
  is_local_min_on (f ∘ g) s b :=
hf.comp_tendsto (hg b hb)

lemma is_local_max.comp_continuous_on [topological_space δ] {s : set δ} {g : δ → α} {b : δ}
  (hf : is_local_max f (g b)) (hg : continuous_on g s) (hb : b ∈ s) :
  is_local_max_on (f ∘ g) s b :=
hf.comp_tendsto (hg b hb)

lemma is_local_extr.comp_continuous_on [topological_space δ] {s : set δ} (g : δ → α) {b : δ}
  (hf : is_local_extr f (g b)) (hg : continuous_on g s) (hb : b ∈ s) :
  is_local_extr_on (f ∘ g) s b :=
hf.elim (λ hf, (hf.comp_continuous_on hg hb).is_extr)
  (λ hf, (is_local_max.comp_continuous_on hf hg hb).is_extr)

lemma is_local_min_on.comp_continuous_on [topological_space δ] {t : set α} {s : set δ} {g : δ → α}
  {b : δ} (hf : is_local_min_on f t (g b)) (hst : s ⊆ g ⁻¹' t) (hg : continuous_on g s)
  (hb : b ∈ s) :
  is_local_min_on (f ∘ g) s b :=
hf.comp_tendsto (tendsto_nhds_within_mono_right (image_subset_iff.mpr hst)
  (continuous_within_at.tendsto_nhds_within_image (hg b hb)))

lemma is_local_max_on.comp_continuous_on [topological_space δ] {t : set α} {s : set δ} {g : δ → α}
  {b : δ} (hf : is_local_max_on f t (g b)) (hst : s ⊆ g ⁻¹' t) (hg : continuous_on g s)
  (hb : b ∈ s) :
  is_local_max_on (f ∘ g) s b :=
hf.comp_tendsto (tendsto_nhds_within_mono_right (image_subset_iff.mpr hst)
  (continuous_within_at.tendsto_nhds_within_image (hg b hb)))

lemma is_local_extr_on.comp_continuous_on [topological_space δ] {t : set α} {s : set δ} (g : δ → α)
  {b : δ} (hf : is_local_extr_on f t (g b)) (hst : s ⊆ g ⁻¹' t) (hg : continuous_on g s)
  (hb : b ∈ s) :
  is_local_extr_on (f ∘ g) s b :=
hf.elim (λ hf, (hf.comp_continuous_on hst hg hb).is_extr)
  (λ hf, (is_local_max_on.comp_continuous_on hf hst hg hb).is_extr)

end preorder

/-! ### Pointwise addition -/
section ordered_add_comm_monoid

variables [ordered_add_comm_monoid β] {f g : α → β} {a : α} {s : set α} {l : filter α}

lemma is_local_min.add (hf : is_local_min f a) (hg : is_local_min g a) :
  is_local_min (λ x, f x + g x) a :=
hf.add hg

lemma is_local_max.add (hf : is_local_max f a) (hg : is_local_max g a) :
  is_local_max (λ x, f x + g x) a :=
hf.add hg

lemma is_local_min_on.add (hf : is_local_min_on f s a) (hg : is_local_min_on g s a) :
  is_local_min_on (λ x, f x + g x) s a :=
hf.add hg

lemma is_local_max_on.add (hf : is_local_max_on f s a) (hg : is_local_max_on g s a) :
  is_local_max_on (λ x, f x + g x) s a :=
hf.add hg

end ordered_add_comm_monoid

/-! ### Pointwise negation and subtraction -/

section ordered_add_comm_group

variables [ordered_add_comm_group β] {f g : α → β} {a : α} {s : set α} {l : filter α}

lemma is_local_min.neg (hf : is_local_min f a) : is_local_max (λ x, -f x) a :=
hf.neg

lemma is_local_max.neg (hf : is_local_max f a) : is_local_min (λ x, -f x) a :=
hf.neg

lemma is_local_extr.neg (hf : is_local_extr f a) : is_local_extr (λ x, -f x) a :=
hf.neg

lemma is_local_min_on.neg (hf : is_local_min_on f s a) : is_local_max_on (λ x, -f x) s a :=
hf.neg

lemma is_local_max_on.neg (hf : is_local_max_on f s a) : is_local_min_on (λ x, -f x) s a :=
hf.neg

lemma is_local_extr_on.neg (hf : is_local_extr_on f s a) : is_local_extr_on (λ x, -f x) s a :=
hf.neg

lemma is_local_min.sub (hf : is_local_min f a) (hg : is_local_max g a) :
  is_local_min (λ x, f x - g x) a :=
hf.sub hg

lemma is_local_max.sub (hf : is_local_max f a) (hg : is_local_min g a) :
  is_local_max (λ x, f x - g x) a :=
hf.sub hg

lemma is_local_min_on.sub (hf : is_local_min_on f s a) (hg : is_local_max_on g s a) :
  is_local_min_on (λ x, f x - g x) s a :=
hf.sub hg

lemma is_local_max_on.sub (hf : is_local_max_on f s a) (hg : is_local_min_on g s a) :
  is_local_max_on (λ x, f x - g x) s a :=
hf.sub hg

end ordered_add_comm_group

/-! ### Pointwise `sup`/`inf` -/

section semilattice_sup

variables [semilattice_sup β] {f g : α → β} {a : α} {s : set α} {l : filter α}

lemma is_local_min.sup (hf : is_local_min f a) (hg : is_local_min g a) :
  is_local_min (λ x, f x ⊔ g x) a :=
hf.sup hg

lemma is_local_max.sup (hf : is_local_max f a) (hg : is_local_max g a) :
  is_local_max (λ x, f x ⊔ g x) a :=
hf.sup hg

lemma is_local_min_on.sup (hf : is_local_min_on f s a) (hg : is_local_min_on g s a) :
  is_local_min_on (λ x, f x ⊔ g x) s a :=
hf.sup hg

lemma is_local_max_on.sup (hf : is_local_max_on f s a) (hg : is_local_max_on g s a) :
  is_local_max_on (λ x, f x ⊔ g x) s a :=
hf.sup hg

end semilattice_sup

section semilattice_inf

variables [semilattice_inf β] {f g : α → β} {a : α} {s : set α} {l : filter α}

lemma is_local_min.inf (hf : is_local_min f a) (hg : is_local_min g a) :
  is_local_min (λ x, f x ⊓ g x) a :=
hf.inf hg

lemma is_local_max.inf (hf : is_local_max f a) (hg : is_local_max g a) :
  is_local_max (λ x, f x ⊓ g x) a :=
hf.inf hg

lemma is_local_min_on.inf (hf : is_local_min_on f s a) (hg : is_local_min_on g s a) :
  is_local_min_on (λ x, f x ⊓ g x) s a :=
hf.inf hg

lemma is_local_max_on.inf (hf : is_local_max_on f s a) (hg : is_local_max_on g s a) :
  is_local_max_on (λ x, f x ⊓ g x) s a :=
hf.inf hg

end semilattice_inf

/-! ### Pointwise `min`/`max` -/

section linear_order

variables [linear_order β] {f g : α → β} {a : α} {s : set α} {l : filter α}

lemma is_local_min.min (hf : is_local_min f a) (hg : is_local_min g a) :
  is_local_min (λ x, min (f x) (g x)) a :=
hf.min hg

lemma is_local_max.min (hf : is_local_max f a) (hg : is_local_max g a) :
  is_local_max (λ x, min (f x) (g x)) a :=
hf.min hg

lemma is_local_min_on.min (hf : is_local_min_on f s a) (hg : is_local_min_on g s a) :
  is_local_min_on (λ x, min (f x) (g x)) s a :=
hf.min hg

lemma is_local_max_on.min (hf : is_local_max_on f s a) (hg : is_local_max_on g s a) :
  is_local_max_on (λ x, min (f x) (g x)) s a :=
hf.min hg

lemma is_local_min.max (hf : is_local_min f a) (hg : is_local_min g a) :
  is_local_min (λ x, max (f x) (g x)) a :=
hf.max hg

lemma is_local_max.max (hf : is_local_max f a) (hg : is_local_max g a) :
  is_local_max (λ x, max (f x) (g x)) a :=
hf.max hg

lemma is_local_min_on.max (hf : is_local_min_on f s a) (hg : is_local_min_on g s a) :
  is_local_min_on (λ x, max (f x) (g x)) s a :=
hf.max hg

lemma is_local_max_on.max (hf : is_local_max_on f s a) (hg : is_local_max_on g s a) :
  is_local_max_on (λ x, max (f x) (g x)) s a :=
hf.max hg

end linear_order

section eventually

/-! ### Relation with `eventually` comparisons of two functions -/

variables [preorder β] {s : set α}

lemma filter.eventually_le.is_local_max_on {f g : α → β} {a : α} (hle : g ≤ᶠ[𝓝[s] a] f)
  (hfga : f a = g a) (h : is_local_max_on f s a) : is_local_max_on g s a :=
hle.is_max_filter hfga h

lemma is_local_max_on.congr {f g : α → β} {a : α} (h : is_local_max_on f s a)
  (heq : f =ᶠ[𝓝[s] a] g) (hmem : a ∈ s) : is_local_max_on g s a :=
h.congr heq $ heq.eq_of_nhds_within hmem

lemma filter.eventually_eq.is_local_max_on_iff {f g : α → β} {a : α} (heq : f =ᶠ[𝓝[s] a] g)
  (hmem : a ∈ s) : is_local_max_on f s a ↔ is_local_max_on g s a :=
heq.is_max_filter_iff $ heq.eq_of_nhds_within hmem

lemma filter.eventually_le.is_local_min_on {f g : α → β} {a : α} (hle : f ≤ᶠ[𝓝[s] a] g)
  (hfga : f a = g a) (h : is_local_min_on f s a) : is_local_min_on g s a :=
hle.is_min_filter hfga h

lemma is_local_min_on.congr {f g : α → β} {a : α} (h : is_local_min_on f s a)
  (heq : f =ᶠ[𝓝[s] a] g) (hmem : a ∈ s) : is_local_min_on g s a :=
h.congr heq $ heq.eq_of_nhds_within hmem

lemma filter.eventually_eq.is_local_min_on_iff {f g : α → β} {a : α} (heq : f =ᶠ[𝓝[s] a] g)
  (hmem : a ∈ s) : is_local_min_on f s a ↔ is_local_min_on g s a :=
heq.is_min_filter_iff $ heq.eq_of_nhds_within hmem

lemma is_local_extr_on.congr {f g : α → β} {a : α} (h : is_local_extr_on f s a)
  (heq : f =ᶠ[𝓝[s] a] g) (hmem : a ∈ s) : is_local_extr_on g s a :=
h.congr heq $ heq.eq_of_nhds_within hmem

lemma filter.eventually_eq.is_local_extr_on_iff {f g : α → β} {a : α} (heq : f =ᶠ[𝓝[s] a] g)
  (hmem : a ∈ s) : is_local_extr_on f s a ↔ is_local_extr_on g s a :=
heq.is_extr_filter_iff $ heq.eq_of_nhds_within hmem

lemma filter.eventually_le.is_local_max {f g : α → β} {a : α} (hle : g ≤ᶠ[𝓝 a] f) (hfga : f a = g a)
  (h : is_local_max f a) : is_local_max g a :=
hle.is_max_filter hfga h

lemma is_local_max.congr {f g : α → β} {a : α} (h : is_local_max f a) (heq : f =ᶠ[𝓝 a] g) :
  is_local_max g a :=
h.congr heq heq.eq_of_nhds

lemma filter.eventually_eq.is_local_max_iff {f g : α → β} {a : α} (heq : f =ᶠ[𝓝 a] g) :
  is_local_max f a ↔ is_local_max g a :=
heq.is_max_filter_iff heq.eq_of_nhds

lemma filter.eventually_le.is_local_min {f g : α → β} {a : α} (hle : f ≤ᶠ[𝓝 a] g) (hfga : f a = g a)
  (h : is_local_min f a) : is_local_min g a :=
hle.is_min_filter hfga h

lemma is_local_min.congr {f g : α → β} {a : α} (h : is_local_min f a) (heq : f =ᶠ[𝓝 a] g) :
  is_local_min g a :=
h.congr heq heq.eq_of_nhds

lemma filter.eventually_eq.is_local_min_iff {f g : α → β} {a : α} (heq : f =ᶠ[𝓝 a] g) :
  is_local_min f a ↔ is_local_min g a :=
heq.is_min_filter_iff heq.eq_of_nhds

lemma is_local_extr.congr {f g : α → β} {a : α} (h : is_local_extr f a) (heq : f =ᶠ[𝓝 a] g) :
  is_local_extr g a :=
h.congr heq heq.eq_of_nhds

lemma filter.eventually_eq.is_local_extr_iff {f g : α → β} {a : α} (heq : f =ᶠ[𝓝 a] g) :
  is_local_extr f a ↔ is_local_extr g a :=
heq.is_extr_filter_iff heq.eq_of_nhds

end eventually


/- 9/20/18
    Preliminary definitions and some functions for working with languages and first-order logic.
    Much of the structure and methods are taken from Russell O'Connor's proof of Goedel's incompleteness theorem, at http://r6.ca/Goedel/goedel1.html
    Andrew Tindall-/

/- 2018-09-21T13:12:55
   Added some proof rules more closely reflecting rules of deduction in Prop
   We should double-check that the additional constructors true, false, and and or have been taken care of appropriately in the implementation of de Bruijn indices.
   ---Jesse -/

structure Language :=
    language :: (relations : Π n : nat, Type) (functions : Π  n : nat, Type) (consts : Type)
variable L : Language

def term_param := unit ⊕ nat
def atm :term_param := sum.inl ()
def vec : nat → term_param := λ n, sum.inr n

inductive term : Π n, Type
    | var : ℕ → term atm
    | const: L.consts → term atm
    | apply : ∀ (n : nat) (f : L.functions n), term (vec n) → term atm
    | nil : term (vec 0)
    | conj : ∀ n, term atm → term (vec n) → term (vec (n + 1))

open term

local notation `⟦` l:(foldr `, ` (h t, ((prod.fst t) + 1, conj t.fst h (prod.snd t))) (0, nil L) `⟧`) := prod.snd l

def term_sizeof :Π n, term L n → ℕ
    | atm (var L n) := 0
    | atm (const c) := 0
    | atm (apply n f ts) :=  n + 2 + term_sizeof (vec n) ts
    | (sum.inr 0) (nil L) := 0
    | (sum.inr (n+1)) (conj m t ts) := 1 + term_sizeof atm t + term_sizeof (vec m) ts

instance term_has_sizeof { n: term_param}  : has_sizeof (term L n) := ⟨ term_sizeof L n ⟩

inductive formula : Type
    | true : formula
    | false : formula
    | equal : term L atm → term L atm → formula
    | atomic : ∀ (n : nat) (r : L.relations n), term L (vec n) → formula
    | and : formula → formula → formula
    | or : formula → formula → formula
    | imp : formula → formula → formula
    | not : formula → formula
    | all : formula → formula
open formula

def andf := λ (f : formula L) g, not (imp f (not g))

reserve infix `⟾`:10
infix ⟾ := imp

reserve infix `⇔`:15
local infix ⇔ := λ (f : formula L) g, andf L (f ⟾ g) (g ⟾ f)

reserve infix `≍`:20
infix ≍ := equal

def free_vars_term : Π n, ℕ → (term L n) → list ℕ
    | atm m (var L k) :=if k > m then [k - m] else []
    | atm m (const c) := []
    | atm m (apply n f ts) := free_vars_term (vec n) m ts
    | (sum.inr 0) _ _ := []
    | (sum.inr (n+1)) m (conj _ t ts) := list.union (free_vars_term atm m t) (free_vars_term (vec n) m ts)

def free_vars_formula : ℕ → formula L → list ℕ
    | m (equal t1 t2) := list.union (free_vars_term L atm m t1) (free_vars_term L atm m t2)
    | m (atomic n r ts) := free_vars_term L (vec n) m ts
    | m (and t1 t2) := list.union(free_vars_formula m t1) (free_vars_formula m t2)
    | m (or t1 t2) := list.union(free_vars_formula m t1) (free_vars_formula m t2)
    | m (imp f1 f2) := list.union (free_vars_formula m f1) (free_vars_formula m f2)
    | m (not f) := free_vars_formula m f
    | m (all  f) := free_vars_formula (m+1) f
    | m (false L) := []
    | m (true L) := []

def raise_depth_term : ∀ n, term L n → ℕ  → term L n
    | atm (var L n) m := var L (n+m)
    | atm (const c) _ := const c
    | atm (apply n f ts) m := apply n f (raise_depth_term (vec n) ts m)
    | (sum.inr 0) _ _ := nil L
    | (sum.inr (n+1)) (conj _ t ts) m := conj n (raise_depth_term atm t m) (raise_depth_term (vec n) ts m)
def raise_depth_formula : formula L → ℕ → formula L
    | (equal t1 t2) m := equal (raise_depth_term L atm t1 m) (raise_depth_term L atm t2 m)
    | (atomic n r ts) m := atomic n r (raise_depth_term L (vec n) ts m)
    | (and t1 t2) m :=  and (raise_depth_formula t1 m) (raise_depth_formula t2 m)
    | (or t1 t2) m :=  or (raise_depth_formula t1 m) (raise_depth_formula t2 m)
    | (imp f1 f2) m := imp (raise_depth_formula f1 m) (raise_depth_formula f2 m)
    | (not f) m := not (raise_depth_formula f m)
    | (all f) m := all (raise_depth_formula f m)
    | (false L) m := (false L)
    | (true L) m := (true L)

def lower_depth_term : ∀ n, term L n → ℕ  → term L n
    | atm (var L n) m := var L (n-m)
    | atm (const c) _ := const c
    | atm (apply n f ts) m := apply n f (lower_depth_term (vec n) ts m)
    | (sum.inr 0) _ _ := nil L
    | (sum.inr (n+1)) (conj _ t ts) m := conj n (lower_depth_term atm t m) (lower_depth_term (vec n) ts m)
def lower_depth_formula : formula L → ℕ → formula L
    | (equal t1 t2) m := equal (lower_depth_term L atm t1 m) (lower_depth_term L atm t2 m)
    | (atomic n r ts) m := atomic n r (lower_depth_term L (vec n) ts m)
    | (and t1 t2) m :=  and (lower_depth_formula t1 m) (lower_depth_formula t2 m)
    | (or t1 t2) m :=  or (lower_depth_formula t1 m) (lower_depth_formula t2 m)
    | (imp f1 f2) m := imp (lower_depth_formula f1 m) (lower_depth_formula f2 m)
    | (not f) m := not (lower_depth_formula f m)
    | (all f) m := all (lower_depth_formula f m)
    | (false L) m := (false L)
    | (true L) m := (true L)

lemma lt_add : ∀ (n m k : ℕ), n < m → n < k + m:= begin {intros n m k n_lt_m, induction k, rw zero_add,exact n_lt_m, rw nat.succ_add, apply nat.lt_succ_of_lt,exact k_ih} end
lemma add_lt : ∀ (n m k : ℕ), n < m → n < m + k := begin {intros n m k n_lt_m, induction k, rw add_zero,exact n_lt_m, rw nat.add_comm, rw nat.succ_add, apply nat.lt_succ_of_lt, rw nat.add_comm, exact k_ih} end

def substitute_term : Π (n : term_param), term L n → nat × term L atm → nat  → term L n
    | atm (var L n) (x, t') m := if (x+m) = n then raise_depth_term L atm t' m else var L n
    | atm (const c) _ _ := const c
    | atm (apply n f ts) (x,t') m := have term_sizeof L (sum.inr n) ts < term_sizeof L atm (apply n f ts), from
        begin
            simp [term_sizeof],
            apply lt_add,
            apply nat.lt_add_of_pos_right,
            apply nat.zero_lt_succ
        end,
     apply n f (substitute_term (sum.inr n) ts (x,t') m)
    | (sum.inr 0) _ _ _ := nil L
    | (sum.inr (n+1)) (conj k t ts) (x,t') m := have term_sizeof L atm t < term_sizeof L (sum.inr (k + 1)) (conj k t ts), from
        begin
            simp [term_sizeof, nat.succ_add],
            apply nat.lt_of_succ_le, apply nat.succ_le_succ,
            apply nat.le_add_right
        end,
        have term_sizeof L (vec k) ts < term_sizeof L (sum.inr (k + 1)) (conj k t ts), from
        begin
            simp [term_sizeof, nat.succ_add],
            apply nat.lt_of_succ_le, apply nat.succ_le_succ,
            apply nat.le_add_left
        end,
    conj n (substitute_term atm t (x,t') m) (substitute_term (vec n) ts (x,t') m)
using_well_founded {rel_tac := λ _ _, `[exact ⟨_, measure_wf psigma.sizeof⟩]}

/- I think we don't need these but I didn't want to delete them yet

def var_in_term : ∀ n, ℕ → ℕ → term L n → Prop
    | atm x m (var L y) := (x+m) = y
    | atm _ _ (const c) := false
    | atm x m (apply n f ts) := var_in_term (vec n) x m ts
    | (sum.inr 0) _ _ _ := false
    | (sum.inr (n+1)) x m (conj _ t ts) := var_in_term atm x m t ∨ var_in_term (vec n) x m ts

def var_in_formula : ℕ → ℕ → formula L → Prop
    | x m (equal t t') := var_in_term L atm x m t ∨ var_in_term L atm x m t'
    | x m (atomic n r ts) := var_in_term L (vec n) x m ts
    | x m (imp f f') := var_in_formula x m f ∨ var_in_formula x m f'
    | x m (not f) := var_in_formula x m f
    | x m (all f) := var_in_formula x (m+1) f
-/

def substitute_formula : formula L → (nat × term L atm) → ℕ → formula L
    | (equal t t') pair m := equal (substitute_term L atm t pair m) (substitute_term L atm t' pair m)
    | (atomic n r t) pair m := atomic n r (substitute_term L (vec n) t pair m)
    | (and t1 t2) pair m :=  and (substitute_formula t1 pair m) (substitute_formula t2 pair m)
    | (or t1 t2) pair m :=  or (substitute_formula t1 pair m) (substitute_formula t2 pair m)
    | (imp f f') pair m := imp (substitute_formula f pair m) (substitute_formula f' pair m)
    | (not f) pair m := not (substitute_formula f pair m)
    | (all f) pair m := substitute_formula f pair (m+1)
    | (false L) pair m := (false L)
    | (true L) pair m := (true L)

/-these are helper functions for constructing the family of extensionality axioms. even_vec n creates the term ⟦ var 0, var 2, ... var 2n ⟧ and odd_vec n creates ⟦ var 1, ... var 2n+1 ⟧ -/

def vec_length_n_diff_two : ∀ n, ℕ →  term L (vec n)
    | 0 hd := nil L
    | (n+1) hd := conj n (var L hd) (vec_length_n_diff_two n (hd+2))

def even_vec := λ n, vec_length_n_diff_two L n 0
def odd_vec := λ n, vec_length_n_diff_two L n 1

def axm_eq' : ∀ m, formula L → formula L
    | 0 f := (((var L 0) ≍ (var L 1)) ⟾ f)
    | (m+1) f := axm_eq' m (((var L (2 * (m + 1))) ≍ (var L (2 * (m + 1) + 1))) ⟾ f)

def axm_eq_4 : ∀ n, L.functions n → formula L
    | 0 f := ((apply 0 f (nil L)) ≍ (apply 0 f (nil L)))
    | (n+1) f := axm_eq' L n ((apply (n+1) f (even_vec L (n+1)) ≍ (apply (n+1) f (odd_vec L (n+1)))))

def axm_eq_5 : ∀ n, L.relations n → formula L
    | 0 r := ((atomic 0 r (nil L)) ⇔ (atomic 0 r (nil L)))
    | (n+1) r := axm_eq' L n ((atomic (n+1) r (even_vec L (n+1))) ⇔ (atomic (n+1) r (odd_vec L (n+1))))

-- L.true must be interpreted as true --- need to have reified true for soundness

inductive prf : list (formula L) → formula L → Type
    | true_intro : ∀ as, prf as (true L) -- true is vacuously provable
    | false_elim : ∀ a, prf [false L] a  -- principle of explosion
    | not_elim : ∀ a, prf [not a] (a ⟾ (false L))
    | not_intro :∀ a, prf [a ⟾ false L] (not a)
    | and_eliml : ∀ a b, prf [formula.and a b] a -- if we know A ∧ B, then we know A
    | and_elimr : ∀ a b, prf [formula.and a b] b -- if we know A ∧ B, then we know B
    | and_intro : ∀ as, prf as (as.foldr (formula.and) (true L)) -- if A1...An are known, then true ∧ A1 ∧ ... ∧ An is provable (and true ∧ B → B) by modus ponens and and_elim
    | or_intro : ∀ a b, prf [a] (formula.or a b) -- if we know A is true, then for any B, A ∨ B is true.
    | axm : ∀ a, prf [a] (true L ⟾ a) -- equivalent to O'Connor's axm by MP
    | mp : ∀ axm axm' f g, prf axm (imp f g) → prf axm' f → prf (axm ++ axm') g
--    | gen : ∀ axm f, prf axm f → prf axm (all (raise_depth_formula L f 1)) --- this follows from all2 and using and_intro to move axm to the right
    | imp1 : ∀ f g, prf [] (f ⟾ (g ⟾ f))
    | imp2 : ∀ f g h, prf [] (f ⟾ (g ⟾ h) ⟾ ((f ⟾ g) ⟾ (f ⟾ h)))
--    | cp : ∀ f g,  prf [] (((not f) ⟾ (not g)) ⟾ (g ⟾ f)) --- until we implement tautology reflection, we should just have double-negation elimination outright
    | dne : ∀ f, prf [] ((not (not f)) ⟾ f)
    | forall_elim : ∀ f t, prf [] ((all f) ⟾ (raise_depth_formula L (substitute_formula L f (0,t) 0)) 1)
    | all2 : ∀ f g, prf [] ((all (f ⟾ g) ⟾ ((all f) ⟾ (all g)))) -- TODO: this seems fishy --- why aren't we able to only quantify over _some_ of the free variables, if we're allowing open variables in the proof system in the first place?
    | eq1 : prf [] ((var L 0) ≍ (var L 0))
    | eq2 : prf [] (( (var L 0) ≍ (var L 1)) ⟾ ((var L 1) ≍ (var L 0)))
    | eq3 : prf [] (((var L 0) ≍ (var L 1)) ⟾ (((var L 1) ≍ (var L 2)) ⟾ ((var L 0) ≍ (var L 2))))
    /- the following two axioms are general equality axioms that are generated for n-ary relations and functions;

            x0 = x1 ⇒ … ⇒ x2n-2 = x2n-1 ⇒ (R(x0, x2, …, x2n-2) ⇔ R(x1, x3, …, x2n-1))  and
            x0 = x1 ⇒ … ⇒ x2n-2 = x2n-1 ⇒ f(x0, x2, …, x2n-2) = f(x1, x3, …, x2n-1)

            src http://r6.ca/Goedel/goedel1.html
            -/
    | eq4 : ∀ n, ∀  r : L.functions n, prf [] (axm_eq_4 L n r)
    | eq5 : ∀ n, ∀ f : L.relations n, prf [] (axm_eq_5 L n f)


/-
This file sets out definitions of what it means for a CNF formula to encode
a Boolean function. Encodings of non-Boolean functions may require different
definitions.

Authors: Cayden Codel, Marijn Heule, Jeremy Avigad
Carnegie Mellon University
-/

import logic.function.basic

import cnf.literal
import cnf.assignment
import cnf.clause
import cnf.cnf
import cnf.gensym
import basic

open clause cnf assignment gensym
open list function

variables {V : Type*} [decidable_eq V] --[fintype V] --[inhabited V]

-- Alternate definition is (fin n → bool) → bool [which is isomorphic to lists]
def constraint := list bool → bool

-- An encoding is a process that takes a list of vars and a gensym and produces a formula
-- A (possibly updated) gensym is also output
def enc_fn (V : Type*) := list (literal V) → gensym V → cnf V × gensym V

-- Disjointness condition between gensym and lists of literals
def disj (l : list (literal V)) (g : gensym V) := disjoint g.stock (clause.vars l)

theorem disj_left {l : list (literal V)} {g : gensym V} :
  disj l g ↔ ∀ ⦃v⦄, v ∈ clause.vars l → v ∉ g.stock :=
set.disjoint_right

theorem disj_right {l : list (literal V)} {g : gensym V} :
  disj l g ↔ ∀ ⦃v⦄, v ∈ g.stock → v ∉ clause.vars l :=
set.disjoint_left

theorem disj_perm {l : list (literal V)} {g : gensym V} {p : list (literal V) → list (literal V)}
  (hp : ∀ l, perm l (p l)) : disj l g ↔ disj (p l) g :=
begin
  unfold disj,
  rw clause.vars_perm (hp l)
end

namespace constraint

variables (c : constraint) (τ : assignment V) (l : list (literal V))

protected def eval := c (l.map (literal.eval τ))

theorem eval_eq_of_agree_on {τ₁ τ₂} :
  (agree_on τ₁ τ₂ (clause.vars l)) → c.eval τ₁ l = c.eval τ₂ l :=
begin
  -- TODO: feels like a more streamlined proof exists, rather than induction
  intro hagree_on,
  unfold constraint.eval,
  apply congr_arg,
  induction l with l₁ ls ih,
  { simp only [list.map_nil] },
  { simp [clause.vars] at hagree_on,
    simp [ih (agree_on_union_right hagree_on)],
    have := finset.mem_union_left (clause.vars ls) (finset.mem_singleton_self l₁.var),
    exact eval_eq_of_agree_on_of_var_mem hagree_on this }
end

/-! # append -/

-- TODO: why not unfolding with simp?
protected def append (c₁ c₂ : constraint) : constraint := λ l, c₁ l && c₂ l
instance : has_append constraint := ⟨constraint.append⟩

-- TODO better name?
def append_id : constraint := λ l, tt

@[simp] theorem append_id_left_id : append_id ++ c = c :=
begin
  apply funext_iff.mpr,
  intro l,
  simp [append, constraint.append, append_id]
end

@[simp] theorem append_id_right_id : c ++ append_id = c :=
begin
  apply funext_iff.mpr,
  intro l,
  simp [append, constraint.append, append_id]
end

instance : is_left_id constraint has_append.append append_id := ⟨ append_id_left_id ⟩
instance : is_right_id constraint has_append.append append_id := ⟨ append_id_right_id ⟩

theorem append_comm (c₁ c₂ : constraint) : c₁ ++ c₂ = c₂ ++ c₁ :=
begin
  apply funext_iff.mpr,
  intro l,
  simp [append, constraint.append, bool.band_comm]
end

theorem append_tt_iff {c₁ c₂ : constraint} {l : list bool} :
  (c₁ ++ c₂) l = tt ↔ c₁ l = tt ∧ c₂ l = tt :=
by simp [append, constraint.append]

theorem append_eval_tt_iff {c₁ c₂ : constraint} {l : list (literal V)} {τ : assignment V} :
  (c₁ ++ c₂).eval τ l = tt ↔ c₁.eval τ l = tt ∧ c₂.eval τ l = tt :=
by simp [constraint.eval, append_tt_iff]

/-! # fold -/

def fold (l : list constraint) := l.foldr append append_id

@[simp] theorem fold_nil : fold [] = append_id := rfl
@[simp] theorem fold_singleton : fold [c] = c := by simp [fold]

@[simp] theorem fold_cons (l : list constraint) : fold (c :: l) = c ++ fold l :=
by simp [fold]

theorem fold_tt_iff_forall_tt {l : list constraint} {bs : list bool} :
  (fold l) bs = tt ↔ ∀ (c : constraint), c ∈ l → c bs = tt :=
begin
  induction l with c₁ cs ih,
  { simp [append_id] },
  { simp [ih, append, constraint.append] }
end

theorem fold_ff_iff_exists_ff {l : list constraint} {bs : list bool} :
  (fold l) bs = ff ↔ ∃ (c : constraint), c ∈ l ∧ c bs = ff :=
begin
  induction l with c₁ cs ih,
  { simp [append_id] },
  { simp [append, constraint.append],
    split,
    { rintros (h | h),
      { exact ⟨c₁, or.inl rfl, h⟩ },
      { rcases ih.mp h with ⟨c, hmem, hc⟩,
        exact ⟨c, or.inr hmem, hc⟩ } },
    { rintros ⟨c, (rfl | h), hc⟩,
      { exact or.inl hc },
      { exact or.inr (ih.mpr ⟨c, h, hc⟩) } } }
end

/-! # len_check -/

-- Check the length of an input list to a constraint
-- In the case where the function returns false, returns the all false constraint
def len_check (c : constraint) (f : nat → bool) : constraint := λ l, if f (length l) then c l else ff

theorem check_append (c₁ c₂ : constraint) (f : nat → bool) :
  len_check (c₁ ++ c₂) f = len_check c₁ f ++ len_check c₂ f :=
begin
  apply funext_iff.mpr,
  intro l,
  cases hf : f (length l);
  simp [len_check, append, constraint.append, hf]
end

end constraint

namespace encoding

theorem disj_of_disj_cons {lit : literal V} {l : list (literal V)} {g} :
  disj (lit :: l) g → disj l g :=
begin
  intro h,
  unfold disj at h,
  simp [clause.vars] at h,
  rw set.insert_eq at h,
  exact (set.disjoint_union_right.mp h).2
end

theorem disj_fresh_of_disj {l : list (literal V)} {g} : disj l g → disj l g.fresh.2 :=
set.disjoint_of_subset_left (fresh_stock_subset g)

theorem disj_fresh_cons {lit : literal V} {l : list (literal V)} {g} : disj (lit :: l) g →
  disj l g.fresh.2 :=
assume h, disj_fresh_of_disj (disj_of_disj_cons h)

variables (c : constraint) (enc : enc_fn V) (τ : assignment V) (F : cnf V) (l : list (literal V))

protected def append (enc₁ enc₂ : enc_fn V) : enc_fn V := λ l g,
  let (F₁, g₁) := enc₁ l g in
  let (F₂, g₂) := enc₂ l g₁ in
  (F₁ ++ F₂, g₂)

instance : has_append (enc_fn V) := ⟨encoding.append⟩

@[ext] theorem append_ext (enc₁ enc₂ : enc_fn V) : ∀ l g,
  (enc₁ ++ enc₂) l g = ((enc₁ l g).1 ++ (enc₂ l (enc₁ l g).2).1, (enc₂ l (enc₁ l g).2).2) :=
begin
  intros l g,
  simp [append, encoding.append],
  rw [prod.ext_self (enc₁ l g), append._match_2,
    prod.ext_self (enc₂ l (enc₁ l g).2), append._match_1],
  refl
end

def append_id : enc_fn V := λ l g, ⟨[], g⟩

@[simp] theorem append_id_left_id : append_id ++ enc = enc :=
begin
  apply funext_iff.mpr, intro l,
  apply funext_iff.mpr, intro g,
  simp [append_ext, append_id]
end

@[simp] theorem append_id_right_id : enc ++ append_id = enc :=
begin
  apply funext_iff.mpr, intro l,
  apply funext_iff.mpr, intro g,
  simp [append_ext, append_id]
end

instance : is_left_id (enc_fn V) has_append.append append_id := ⟨ append_id_left_id ⟩
instance : is_right_id (enc_fn V) has_append.append append_id := ⟨ append_id_right_id ⟩

/-! # fold -/

def fold (l : list (enc_fn V)) := l.foldr append append_id

@[simp] theorem fold_nil : fold ([] : list (enc_fn V)) = append_id := rfl
@[simp] theorem fold_singleton : fold [enc] = enc := by simp [fold]

@[simp] theorem fold_cons (l : list (enc_fn V)) : fold (enc :: l) = enc ++ fold l :=
by simp [fold]

/-! # encodes -/

/- A CNF formula encodes a Boolean function on a base list of input literals
   if an extending assignment on those literals can be found to satisfy the formula. -/
def formula_encodes := ∀ (τ : assignment V), (c.eval τ l = tt) ↔ ∃ σ, F.eval σ = tt ∧ agree_on τ σ (clause.vars l)

/- An encoding function encodes a Boolean constraint if, when given a disjoint
   gensym, the encodes judgment is satisfied -/
def is_correct := ∀ ⦃l : list (literal V)⦄ ⦃g⦄, disj l g → formula_encodes c (enc l g).1 l

/-! # Well-behavedness -/

/- Encoding functions are well-behaved if the only variables they generate are from the
   input literals and from the gensym, and no more. The gensym is also updated accordingly. -/
def is_wb := ∀ ⦃l : list (literal V)⦄ ⦃g⦄, disj l g →
    (enc l g).2.stock ⊆ g.stock ∧
    ↑(enc l g).1.vars ⊆ ↑(clause.vars l) ∪ (g.stock \ (enc l g).2.stock)

theorem not_mem_form_of_is_wb {enc : enc_fn V} (henc : is_wb enc) :
  ∀ ⦃l : list (literal V)⦄ ⦃g⦄, disj l g →
  ∀ ⦃v : V⦄, v ∉ ↑(clause.vars l) ∪ g.stock → v ∉ (enc l g).1.vars :=
begin
  intros l g hdis v hnmem hv,
  rcases (set.mem_union _ _ _).mp (((henc hdis).2) hv) with (h | h),
  { exact hnmem (set.mem_union_left _ h) },
  { exact hnmem (set.mem_union_right _ h.1 ) }
end

-- Not as strong as it could be?
theorem mem_vars_or_stock_of_is_wb_of_mem {enc : enc_fn V} (henc : is_wb enc) :
  ∀ ⦃l : list (literal V)⦄ ⦃g⦄, disj l g →
  ∀ ⦃v : V⦄, v ∈ (enc l g).1.vars → v ∈ clause.vars l ∨ v ∈ g.stock :=
begin
  intros l g hdis v hmem,
  rcases (set.mem_union _ _ _).mp (((henc hdis).2) hmem) with (h | h),
  { exact or.inl h },
  { exact or.inr h.1 }
end

theorem disj_of_is_wb {enc : enc_fn V} (henc : is_wb enc) :
  ∀ ⦃l : list (literal V)⦄  ⦃g⦄, disj l g →
  disjoint ↑(enc l g).1.vars (enc l g).2.stock :=
begin
  intros l g hdis,
  apply set.disjoint_left.mpr,
  intros v hv,
  rcases (set.mem_union _ _ _).mp ((henc hdis).2 hv) with (hv | hv),
  { exact not_mem_subset (disj_left.mp hdis hv) (henc hdis).1 },
  { exact hv.2 }
end

theorem disj_left_of_is_wb {enc : enc_fn V} (henc : is_wb enc) :
  ∀ ⦃l : list (literal V)⦄ ⦃g⦄, disj l g →
  ∀ ⦃v⦄, v ∈ (enc l g).1.vars → v ∉ (enc l g).2.stock :=
assume l g hdis, set.disjoint_left.mp (disj_of_is_wb henc hdis)

theorem disj_right_of_is_wb {enc : enc_fn V} (henc : is_wb enc) :
  ∀ ⦃l : list (literal V)⦄ ⦃g⦄, disj l g →
  ∀ ⦃v⦄, v ∈ (enc l g).2.stock → v ∉ (enc l g).1.vars :=
assume l g hdis, set.disjoint_right.mp (disj_of_is_wb henc hdis)

theorem append_is_wb {e₁ e₂ : enc_fn V} :
  is_wb e₁ → is_wb e₂ → is_wb (e₁ ++ e₂) :=
begin
  intros h₁ h₂ l g hdis,
  have h₃ := (h₁ hdis).1,
  have h₄ : disj l (e₁ l g).2, from set.disjoint_of_subset_left h₃ hdis,
  split,
  { simp [append_ext],
    exact subset_trans (h₂ h₄).1 h₃ },
  { simp [append_ext],
    have hl : ↑(e₁ l g).1.vars ⊆ ↑(clause.vars l) ∪ (g.stock \ (e₂ l (e₁ l g).2).2.stock),
    { exact subset_trans (h₁ hdis).2 (set.union_subset_union_right ↑(clause.vars l)
        (set.diff_subset_diff_right (h₂ h₄).1)) },
    have hr : ↑(e₂ l (e₁ l g).2).1.vars ⊆ ↑(clause.vars l) ∪ (g.stock \ (e₂ l (e₁ l g).2).2.stock),
    { exact subset_trans (h₂ h₄).2 (set.union_subset_union_right ↑(clause.vars l) (set.diff_subset_diff_left h₃)) },
    exact ⟨hl, hr⟩ }
end

theorem subset_union_of_wb {enc : enc_fn V} :
  is_wb enc → ∀ ⦃l : list (literal V)⦄ ⦃g⦄, disj l g →
  ↑((enc l g).1.vars) ⊆ ↑(clause.vars l) ∪ g.stock :=
begin
  intros h l g hdis v hv,
  have := (h hdis).2,
  rcases (set.mem_union _ _ _).mp (this hv) with (hv | hv),
  { exact set.mem_union_left _ hv },
  { exact set.mem_union_right _ ((set.mem_diff _).mp hv).1 }
end

-- In an abuse of notation, we combine the is_correct and is_wb judgments together
--   to simplify theorems and package together by what we mean when an
--   encoding function encodes a constraint
def encodes := is_correct c enc ∧ is_wb enc

theorem is_correct_of_encodes : encodes c enc → is_correct c enc :=
assume h l g hdis, h.1 hdis

theorem is_wb_of_encodes : encodes c enc → is_wb enc :=
assume h l g hdis, h.2 hdis

theorem append_id_is_wb : is_wb (append_id : enc_fn V) :=
assume l g hdis, by simp [append_id]

@[simp] theorem encodes_id : encodes constraint.append_id (append_id : enc_fn V) :=
begin
  split,
  { intros l g hdis τ,
    simp [constraint.append_id, constraint.eval, append_id],
    use τ },
  { exact append_id_is_wb }
end

theorem encodes_append {c₁ c₂ : constraint} {enc₁ enc₂ : enc_fn V} :
  encodes c₁ enc₁ → encodes c₂ enc₂ → encodes (c₁ ++ c₂) (enc₁ ++ enc₂) :=
begin
  rintros h₁ h₂, split,
  { intros l g hdis τ, split,
    { intro he,
      rcases (h₁.1 hdis τ).mp (constraint.append_tt_iff.mp he).1 with ⟨σ₁, he₁, hs₁⟩,
      have := set.disjoint_of_subset_left (h₁.2 hdis).1 hdis,
      rcases (h₂.1 this τ).mp (constraint.append_tt_iff.mp he).2 with ⟨σ₂, he₂, hs₂⟩,
      use aite (enc₁ l g).1.vars σ₁ σ₂,
      split,
      { rw eval_tt_iff_forall_clause_eval_tt,
        intros c hc,
        simp [append_ext] at hc,
        rcases hc with (hc | hc),
        { revert hc c,
          rw ← eval_tt_iff_forall_clause_eval_tt,
          rw cnf.eval_eq_of_agree_on (aite_agree_on (enc₁ l g).1.vars σ₁ σ₂),
          exact he₁ },
        { have := eval_tt_iff_forall_clause_eval_tt.mp he₂ _ hc,
          rcases eval_tt_iff_exists_literal_eval_tt.mp this with ⟨lit, hmem, hlit⟩,
          rw eval_tt_iff_exists_literal_eval_tt,
          use [lit, hmem],
          by_cases hm : lit.var ∈ (enc₁ l g).1.vars,
          { rcases h₁.2 hdis with ⟨hf₁, hf₂⟩,
            rcases set.mem_or_mem_of_mem_union (hf₂ hm) with (h | h),
            { -- Say eval under σ₁ = τ = σ₂
              rw aite_pos_lit hm,
              rw ← eval_eq_of_agree_on_of_var_mem hs₁ h,
              rw eval_eq_of_agree_on_of_var_mem hs₂ h,
              exact hlit },
            { -- Derive contradiction that lit is not a member of e₁'s fresh vars
              rcases (h₂.2 (set.disjoint_of_subset_left (h₁.2 hdis).1 hdis)) with ⟨_, hf₄⟩,
              have := mem_vars_iff_exists_mem_clause_and_mem.mpr ⟨c, hc, mem_vars_of_mem hmem⟩,
              rcases set.mem_or_mem_of_mem_union (hf₄ this) with (hl | hl),
              { rw aite_pos_lit hm,
                rw ← eval_eq_of_agree_on_of_var_mem hs₁ hl,
                rw eval_eq_of_agree_on_of_var_mem hs₂ hl,
                exact hlit },
              { rw set.mem_diff at hl h,
                exact absurd hl.1 h.2 } } },
          { rw aite_neg_lit hm, exact hlit } } },
      { exact aite_agree_on_of_agree_on_of_agree_on hs₁ hs₂ _ } },
    { rintros ⟨σ, he, hs⟩,
      rw constraint.append_eval_tt_iff,
      simp [append_ext, cnf.eval_append] at he,
      split,
      { apply (h₁.1 hdis τ).mpr,
        use [σ, he.1, hs] },
      { apply (h₂.1 (set.disjoint_of_subset_left (h₁.2 hdis).1 hdis) τ).mpr,
        use [σ, he.2, hs], } } },
  { exact append_is_wb h₁.2 h₂.2 }
end

/-! # len_check -/

def len_check (f : nat → bool) : enc_fn V :=
  λ l g, if f (length l) then enc l g else ⟨[[]], g⟩

theorem check_encodes_of_encodes (f : nat → bool) :
  encodes c enc → encodes (constraint.len_check c f) (len_check enc f) :=
begin
  intro henc,
  split,
  { intros l g hdis τ,
    cases hf : f (length l);
    simp [len_check, hf, constraint.eval, constraint.len_check],
    { exact ((henc.1 hdis) τ) } },
  { intros l g hdis,
    cases hf : f (length l);
    simp [len_check, constraint.len_check, hf],
    exact (henc.2 hdis) }
end

/-! # misc -/

theorem encodes_fold_fold {α : Type*} {f : α → constraint} {g : α → enc_fn V} {l : list α} :
  (∀ ⦃a : α⦄, a ∈ l → encodes (f a) (g a)) → encodes (constraint.fold (l.map f)) (fold (l.map g)) :=
begin
  induction l with a as ih,
  { simp },
  { intro h,
    simp,
    apply encodes_append,
    { exact h (mem_cons_self a as) },
    { apply ih,
      intros b hb,
      exact h (mem_cons_of_mem _ hb) } }
end

theorem filter_by_idx_is_wb (p : nat → Prop) [decidable_pred p] {enc : enc_fn V} :
  is_wb enc → is_wb (enc ∘ filter_by_idx p) :=
begin
  intros h₁ l g hdis,
  have h₂ := set.disjoint_of_subset_right (clause.vars_subset_of_subset (filter_by_idx_subset p l)) hdis,
  simp,
  split,
  { exact (h₁ h₂).1 },
  { have h₃ := (h₁ h₂).2,
    intros v hv,
    rw set.mem_union,
    rcases (set.mem_union _ _ _).mp (h₃ hv) with (hv | hv),
    { exact or.inl ((clause.vars_subset_of_subset (filter_by_idx_subset p l)) hv) },
    { exact set.mem_union_right _ hv } }
end

theorem filter_by_idx_encodes_of_encodes (p : nat → Prop) [decidable_pred p]
  {c : constraint} {enc : enc_fn V} :
  encodes c enc → encodes (c ∘ (filter_by_idx p)) (enc ∘ (filter_by_idx p)) :=
begin
  intro h,
  split,
  { intros l g hdis τ,
    simp [constraint.eval],
    rw map_filter_by_idx,
    have hw := filter_by_idx_subset p l,
    have hdis' := set.disjoint_of_subset_right (clause.vars_subset_of_subset hw) hdis,
    split,
    { intro hc,
      rcases (h.1 hdis' τ).mp hc with ⟨σ, heval, hs⟩,
      use aite (cnf.vars (enc (filter_by_idx p l) g).1) σ τ,
      split,
      { rw cnf.eval_eq_of_agree_on (aite_agree_on _ _ _), exact heval },
      { intros v hv,
        by_cases hvmem : v ∈ (enc (filter_by_idx p l) g).1.vars,
        { rw aite_pos hvmem,
          rcases (set.mem_union _ _ _).mp ((h.2 hdis').2 hvmem) with (hv₂ | hv₂),
          { exact hs _ hv₂ },
          { have h₉ := set.disjoint_left.mp hdis hv₂.1,
            exact absurd hv h₉ } },
        { rw aite_neg hvmem } } },
    { rintros ⟨σ, heval, hs⟩,
      apply (h.1 hdis' τ).mpr,
      use [σ, heval],
      exact agree_on_subset (clause.vars_subset_of_subset hw) hs } },
  { exact filter_by_idx_is_wb p h.2 }
end

/-! # direct encoding -/

/- Definition of a direct encoding -/
def dir_enc (f : list (literal V) → cnf V) : enc_fn V := λ l g, (f l, g)

end encoding


/-
Copyright (c) 2020 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Yury Kudryashov, Johannes Hölzl, Mario Carneiro, Patrick Massot
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.order.filter.basic
import Mathlib.data.set.countable
import Mathlib.PostPort

universes u_6 l u_1 u_4 u_5 u_2 u_3

namespace Mathlib

/-!
# Filter bases

A filter basis `B : filter_basis α` on a type `α` is a nonempty collection of sets of `α`
such that the intersection of two elements of this collection contains some element of
the collection. Compared to filters, filter bases do not require that any set containing
an element of `B` belongs to `B`.
A filter basis `B` can be used to construct `B.filter : filter α` such that a set belongs
to `B.filter` if and only if it contains an element of `B`.

Given an indexing type `ι`, a predicate `p : ι → Prop`, and a map `s : ι → set α`,
the proposition `h : filter.is_basis p s` makes sure the range of `s` bounded by `p`
(ie. `s '' set_of p`) defines a filter basis `h.filter_basis`.

If one already has a filter `l` on `α`, `filter.has_basis l p s` (where `p : ι → Prop`
and `s : ι → set α` as above) means that a set belongs to `l` if and
only if it contains some `s i` with `p i`. It implies `h : filter.is_basis p s`, and
`l = h.filter_basis.filter`. The point of this definition is that checking statements
involving elements of `l` often reduces to checking them on the basis elements.

We define a function `has_basis.index (h : filter.has_basis l p s) (t) (ht : t ∈ l)` that returns
some index `i` such that `p i` and `s i ⊆ t`. This function can be useful to avoid manual
destruction of `h.mem_iff.mpr ht` using `cases` or `let`.

This file also introduces more restricted classes of bases, involving monotonicity or
countability. In particular, for `l : filter α`, `l.is_countably_generated` means
there is a countable set of sets which generates `s`. This is reformulated in term of bases,
and consequences are derived.

## Main statements

* `has_basis.mem_iff`, `has_basis.mem_of_superset`, `has_basis.mem_of_mem` : restate `t ∈ f` in terms
  of a basis;
* `basis_sets` : all sets of a filter form a basis;
* `has_basis.inf`, `has_basis.inf_principal`, `has_basis.prod`, `has_basis.prod_self`,
  `has_basis.map`, `has_basis.comap` : combinators to construct filters of `l ⊓ l'`,
  `l ⊓ 𝓟 t`, `l ×ᶠ l'`, `l ×ᶠ l`, `l.map f`, `l.comap f` respectively;
* `has_basis.le_iff`, `has_basis.ge_iff`, has_basis.le_basis_iff` : restate `l ≤ l'` in terms
  of bases.
* `has_basis.tendsto_right_iff`, `has_basis.tendsto_left_iff`, `has_basis.tendsto_iff` : restate
  `tendsto f l l'` in terms of bases.
* `is_countably_generated_iff_exists_antimono_basis` : proves a filter is
  countably generated if and only if it admis a basis parametrized by a
  decreasing sequence of sets indexed by `ℕ`.
* `tendsto_iff_seq_tendsto ` : an abstract version of "sequentially continuous implies continuous".

## Implementation notes

As with `Union`/`bUnion`/`sUnion`, there are three different approaches to filter bases:

* `has_basis l s`, `s : set (set α)`;
* `has_basis l s`, `s : ι → set α`;
* `has_basis l p s`, `p : ι → Prop`, `s : ι → set α`.

We use the latter one because, e.g., `𝓝 x` in an `emetric_space` or in a `metric_space` has a basis
of this form. The other two can be emulated using `s = id` or `p = λ _, true`.

With this approach sometimes one needs to `simp` the statement provided by the `has_basis`
machinery, e.g., `simp only [exists_prop, true_and]` or `simp only [forall_const]` can help
with the case `p = λ _, true`.
-/

/-- A filter basis `B` on a type `α` is a nonempty collection of sets of `α`
such that the intersection of two elements of this collection contains some element
of the collection. -/
structure filter_basis (α : Type u_6)
where
  sets : set (set α)
  nonempty : set.nonempty sets
  inter_sets : ∀ {x y : set α}, x ∈ sets → y ∈ sets → ∃ (z : set α), ∃ (H : z ∈ sets), z ⊆ x ∩ y

protected instance filter_basis.nonempty_sets {α : Type u_1} (B : filter_basis α) : Nonempty ↥(filter_basis.sets B) :=
  set.nonempty.to_subtype (filter_basis.nonempty B)

/-- If `B` is a filter basis on `α`, and `U` a subset of `α` then we can write `U ∈ B` as on paper. -/
protected instance filter_basis.has_mem {α : Type u_1} : has_mem (set α) (filter_basis α) :=
  has_mem.mk fun (U : set α) (B : filter_basis α) => U ∈ filter_basis.sets B

-- For illustration purposes, the filter basis defining (at_top : filter ℕ)

protected instance filter_basis.inhabited : Inhabited (filter_basis ℕ) :=
  { default := filter_basis.mk (set.range set.Ici) sorry sorry }

/-- `is_basis p s` means the image of `s` bounded by `p` is a filter basis. -/
structure filter.is_basis {α : Type u_1} {ι : Type u_4} (p : ι → Prop) (s : ι → set α)
where
  nonempty : ∃ (i : ι), p i
  inter : ∀ {i j : ι}, p i → p j → ∃ (k : ι), p k ∧ s k ⊆ s i ∩ s j

namespace filter

namespace is_basis

/-- Constructs a filter basis from an indexed family of sets satisfying `is_basis`. -/
protected def filter_basis {α : Type u_1} {ι : Type u_4} {p : ι → Prop} {s : ι → set α} (h : is_basis p s) : filter_basis α :=
  filter_basis.mk (s '' set_of p) sorry sorry

theorem mem_filter_basis_iff {α : Type u_1} {ι : Type u_4} {p : ι → Prop} {s : ι → set α} (h : is_basis p s) {U : set α} : U ∈ is_basis.filter_basis h ↔ ∃ (i : ι), p i ∧ s i = U :=
  iff.rfl

end is_basis

end filter

namespace filter_basis

/-- The filter associated to a filter basis. -/
protected def filter {α : Type u_1} (B : filter_basis α) : filter α :=
  filter.mk (set_of fun (s : set α) => ∃ (t : set α), ∃ (H : t ∈ B), t ⊆ s) sorry sorry sorry

theorem mem_filter_iff {α : Type u_1} (B : filter_basis α) {U : set α} : U ∈ filter_basis.filter B ↔ ∃ (s : set α), ∃ (H : s ∈ B), s ⊆ U :=
  iff.rfl

theorem mem_filter_of_mem {α : Type u_1} (B : filter_basis α) {U : set α} : U ∈ B → U ∈ filter_basis.filter B :=
  fun (U_in : U ∈ B) => Exists.intro U (Exists.intro U_in (set.subset.refl U))

theorem eq_infi_principal {α : Type u_1} (B : filter_basis α) : filter_basis.filter B = infi fun (s : ↥(sets B)) => filter.principal ↑s := sorry

protected theorem generate {α : Type u_1} (B : filter_basis α) : filter.generate (sets B) = filter_basis.filter B := sorry

end filter_basis

namespace filter

namespace is_basis

/-- Constructs a filter from an indexed family of sets satisfying `is_basis`. -/
protected def filter {α : Type u_1} {ι : Type u_4} {p : ι → Prop} {s : ι → set α} (h : is_basis p s) : filter α :=
  filter_basis.filter (is_basis.filter_basis h)

protected theorem mem_filter_iff {α : Type u_1} {ι : Type u_4} {p : ι → Prop} {s : ι → set α} (h : is_basis p s) {U : set α} : U ∈ is_basis.filter h ↔ ∃ (i : ι), p i ∧ s i ⊆ U := sorry

theorem filter_eq_generate {α : Type u_1} {ι : Type u_4} {p : ι → Prop} {s : ι → set α} (h : is_basis p s) : is_basis.filter h = generate (set_of fun (U : set α) => ∃ (i : ι), p i ∧ s i = U) := sorry

end is_basis

/-- We say that a filter `l` has a basis `s : ι → set α` bounded by `p : ι → Prop`,
if `t ∈ l` if and only if `t` includes `s i` for some `i` such that `p i`. -/
structure has_basis {α : Type u_1} {ι : Type u_4} (l : filter α) (p : ι → Prop) (s : ι → set α)
where
  mem_iff' : ∀ (t : set α), t ∈ l ↔ ∃ (i : ι), ∃ (hi : p i), s i ⊆ t

theorem has_basis_generate {α : Type u_1} (s : set (set α)) : has_basis (generate s) (fun (t : set (set α)) => set.finite t ∧ t ⊆ s) fun (t : set (set α)) => ⋂₀t := sorry

/-- The smallest filter basis containing a given collection of sets. -/
def filter_basis.of_sets {α : Type u_1} (s : set (set α)) : filter_basis α :=
  filter_basis.mk (set.sInter '' set_of fun (t : set (set α)) => set.finite t ∧ t ⊆ s) sorry sorry

/-- Definition of `has_basis` unfolded with implicit set argument. -/
theorem has_basis.mem_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} {t : set α} (hl : has_basis l p s) : t ∈ l ↔ ∃ (i : ι), ∃ (hi : p i), s i ⊆ t :=
  has_basis.mem_iff' hl t

theorem has_basis_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} : has_basis l p s ↔ ∀ (t : set α), t ∈ l ↔ ∃ (i : ι), ∃ (hi : p i), s i ⊆ t := sorry

theorem has_basis.ex_mem {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) : ∃ (i : ι), p i := sorry

protected theorem is_basis.has_basis {α : Type u_1} {ι : Type u_4} {p : ι → Prop} {s : ι → set α} (h : is_basis p s) : has_basis (is_basis.filter h) p s := sorry

theorem has_basis.mem_of_superset {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} {t : set α} {i : ι} (hl : has_basis l p s) (hi : p i) (ht : s i ⊆ t) : t ∈ l :=
  iff.mpr (has_basis.mem_iff hl) (Exists.intro i (Exists.intro hi ht))

theorem has_basis.mem_of_mem {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} {i : ι} (hl : has_basis l p s) (hi : p i) : s i ∈ l :=
  has_basis.mem_of_superset hl hi (set.subset.refl (s i))

/-- Index of a basis set such that `s i ⊆ t` as an element of `subtype p`. -/
def has_basis.index {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) (t : set α) (ht : t ∈ l) : Subtype fun (i : ι) => p i :=
  { val := Exists.some sorry, property := sorry }

theorem has_basis.property_index {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} {t : set α} (h : has_basis l p s) (ht : t ∈ l) : p ↑(has_basis.index h t ht) :=
  subtype.property (has_basis.index h t ht)

theorem has_basis.set_index_mem {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} {t : set α} (h : has_basis l p s) (ht : t ∈ l) : s ↑(has_basis.index h t ht) ∈ l :=
  has_basis.mem_of_mem h (has_basis.property_index h ht)

theorem has_basis.set_index_subset {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} {t : set α} (h : has_basis l p s) (ht : t ∈ l) : s ↑(has_basis.index h t ht) ⊆ t :=
  Exists.snd (Exists.some_spec (iff.mp (has_basis.mem_iff h) ht))

theorem has_basis.is_basis {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) : is_basis p s := sorry

theorem has_basis.filter_eq {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) : is_basis.filter (has_basis.is_basis h) = l := sorry

theorem has_basis.eq_generate {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) : l = generate (set_of fun (U : set α) => ∃ (i : ι), p i ∧ s i = U) := sorry

theorem generate_eq_generate_inter {α : Type u_1} (s : set (set α)) : generate s = generate (set.sInter '' set_of fun (t : set (set α)) => set.finite t ∧ t ⊆ s) := sorry

theorem of_sets_filter_eq_generate {α : Type u_1} (s : set (set α)) : filter_basis.filter (filter_basis.of_sets s) = generate s := sorry

theorem has_basis.to_has_basis {α : Type u_1} {ι : Type u_4} {ι' : Type u_5} {l : filter α} {p : ι → Prop} {s : ι → set α} {p' : ι' → Prop} {s' : ι' → set α} (hl : has_basis l p s) (h : ∀ (i : ι), p i → ∃ (i' : ι'), p' i' ∧ s' i' ⊆ s i) (h' : ∀ (i' : ι'), p' i' → ∃ (i : ι), p i ∧ s i ⊆ s' i') : has_basis l p' s' := sorry

theorem has_basis.eventually_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) {q : α → Prop} : filter.eventually (fun (x : α) => q x) l ↔ ∃ (i : ι), p i ∧ ∀ {x : α}, x ∈ s i → q x := sorry

theorem has_basis.frequently_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) {q : α → Prop} : filter.frequently (fun (x : α) => q x) l ↔ ∀ (i : ι), p i → ∃ (x : α), ∃ (H : x ∈ s i), q x := sorry

theorem has_basis.exists_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) {P : set α → Prop} (mono : ∀ {s t : set α}, s ⊆ t → P t → P s) : (∃ (s : set α), ∃ (H : s ∈ l), P s) ↔ ∃ (i : ι), ∃ (hi : p i), P (s i) := sorry

theorem has_basis.forall_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) {P : set α → Prop} (mono : ∀ {s t : set α}, s ⊆ t → P s → P t) : (∀ (s : set α), s ∈ l → P s) ↔ ∀ (i : ι), p i → P (s i) := sorry

theorem has_basis.ne_bot_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) : ne_bot l ↔ ∀ {i : ι}, p i → set.nonempty (s i) :=
  iff.trans (iff.symm forall_sets_nonempty_iff_ne_bot)
    (has_basis.forall_iff hl fun (_x _x_1 : set α) => set.nonempty.mono)

theorem has_basis.eq_bot_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) : l = ⊥ ↔ ∃ (i : ι), p i ∧ s i = ∅ := sorry

theorem basis_sets {α : Type u_1} (l : filter α) : has_basis l (fun (s : set α) => s ∈ l) id :=
  has_basis.mk fun (t : set α) => iff.symm exists_sets_subset_iff

theorem has_basis_self {α : Type u_1} {l : filter α} {P : set α → Prop} : has_basis l (fun (s : set α) => s ∈ l ∧ P s) id ↔ ∀ (t : set α), t ∈ l ↔ ∃ (r : set α), ∃ (H : r ∈ l), P r ∧ r ⊆ t := sorry

/-- If `{s i | p i}` is a basis of a filter `l` and each `s i` includes `s j` such that
`p j ∧ q j`, then `{s j | p j ∧ q j}` is a basis of `l`. -/
theorem has_basis.restrict {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) {q : ι → Prop} (hq : ∀ (i : ι), p i → ∃ (j : ι), p j ∧ q j ∧ s j ⊆ s i) : has_basis l (fun (i : ι) => p i ∧ q i) s := sorry

/-- If `{s i | p i}` is a basis of a filter `l` and `V ∈ l`, then `{s i | p i ∧ s i ⊆ V}`
is a basis of `l`. -/
theorem has_basis.restrict_subset {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) {V : set α} (hV : V ∈ l) : has_basis l (fun (i : ι) => p i ∧ s i ⊆ V) s := sorry

theorem has_basis.has_basis_self_subset {α : Type u_1} {l : filter α} {p : set α → Prop} (h : has_basis l (fun (s : set α) => s ∈ l ∧ p s) id) {V : set α} (hV : V ∈ l) : has_basis l (fun (s : set α) => s ∈ l ∧ p s ∧ s ⊆ V) id := sorry

theorem has_basis.ge_iff {α : Type u_1} {ι' : Type u_5} {l : filter α} {l' : filter α} {p' : ι' → Prop} {s' : ι' → set α} (hl' : has_basis l' p' s') : l ≤ l' ↔ ∀ (i' : ι'), p' i' → s' i' ∈ l := sorry

theorem has_basis.le_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {l' : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) : l ≤ l' ↔ ∀ (t : set α), t ∈ l' → ∃ (i : ι), ∃ (hi : p i), s i ⊆ t := sorry

theorem has_basis.le_basis_iff {α : Type u_1} {ι : Type u_4} {ι' : Type u_5} {l : filter α} {l' : filter α} {p : ι → Prop} {s : ι → set α} {p' : ι' → Prop} {s' : ι' → set α} (hl : has_basis l p s) (hl' : has_basis l' p' s') : l ≤ l' ↔ ∀ (i' : ι'), p' i' → ∃ (i : ι), ∃ (hi : p i), s i ⊆ s' i' := sorry

theorem has_basis.ext {α : Type u_1} {ι : Type u_4} {ι' : Type u_5} {l : filter α} {l' : filter α} {p : ι → Prop} {s : ι → set α} {p' : ι' → Prop} {s' : ι' → set α} (hl : has_basis l p s) (hl' : has_basis l' p' s') (h : ∀ (i : ι), p i → ∃ (i' : ι'), p' i' ∧ s' i' ⊆ s i) (h' : ∀ (i' : ι'), p' i' → ∃ (i : ι), p i ∧ s i ⊆ s' i') : l = l' := sorry

theorem has_basis.inf {α : Type u_1} {ι : Type u_4} {ι' : Type u_5} {l : filter α} {l' : filter α} {p : ι → Prop} {s : ι → set α} {p' : ι' → Prop} {s' : ι' → set α} (hl : has_basis l p s) (hl' : has_basis l' p' s') : has_basis (l ⊓ l') (fun (i : ι × ι') => p (prod.fst i) ∧ p' (prod.snd i))
  fun (i : ι × ι') => s (prod.fst i) ∩ s' (prod.snd i) := sorry

theorem has_basis_principal {α : Type u_1} (t : set α) : has_basis (principal t) (fun (i : Unit) => True) fun (i : Unit) => t := sorry

theorem has_basis.sup {α : Type u_1} {ι : Type u_4} {ι' : Type u_5} {l : filter α} {l' : filter α} {p : ι → Prop} {s : ι → set α} {p' : ι' → Prop} {s' : ι' → set α} (hl : has_basis l p s) (hl' : has_basis l' p' s') : has_basis (l ⊔ l') (fun (i : ι × ι') => p (prod.fst i) ∧ p' (prod.snd i))
  fun (i : ι × ι') => s (prod.fst i) ∪ s' (prod.snd i) := sorry

theorem has_basis.inf_principal {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) (s' : set α) : has_basis (l ⊓ principal s') p fun (i : ι) => s i ∩ s' := sorry

theorem has_basis.inf_basis_ne_bot_iff {α : Type u_1} {ι : Type u_4} {ι' : Type u_5} {l : filter α} {l' : filter α} {p : ι → Prop} {s : ι → set α} {p' : ι' → Prop} {s' : ι' → set α} (hl : has_basis l p s) (hl' : has_basis l' p' s') : ne_bot (l ⊓ l') ↔ ∀ {i : ι}, p i → ∀ {i' : ι'}, p' i' → set.nonempty (s i ∩ s' i') := sorry

theorem has_basis.inf_ne_bot_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {l' : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) : ne_bot (l ⊓ l') ↔ ∀ {i : ι}, p i → ∀ {s' : set α}, s' ∈ l' → set.nonempty (s i ∩ s') :=
  has_basis.inf_basis_ne_bot_iff hl (basis_sets l')

theorem has_basis.inf_principal_ne_bot_iff {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) {t : set α} : ne_bot (l ⊓ principal t) ↔ ∀ {i : ι}, p i → set.nonempty (s i ∩ t) :=
  has_basis.ne_bot_iff (has_basis.inf_principal hl t)

theorem inf_ne_bot_iff {α : Type u_1} {l : filter α} {l' : filter α} : ne_bot (l ⊓ l') ↔ ∀ {s : set α}, s ∈ l → ∀ {s' : set α}, s' ∈ l' → set.nonempty (s ∩ s') :=
  has_basis.inf_ne_bot_iff (basis_sets l)

theorem inf_principal_ne_bot_iff {α : Type u_1} {l : filter α} {s : set α} : ne_bot (l ⊓ principal s) ↔ ∀ (U : set α), U ∈ l → set.nonempty (U ∩ s) :=
  has_basis.inf_principal_ne_bot_iff (basis_sets l)

theorem inf_eq_bot_iff {α : Type u_1} {f : filter α} {g : filter α} : f ⊓ g = ⊥ ↔ ∃ (U : set α), ∃ (H : U ∈ f), ∃ (V : set α), ∃ (H : V ∈ g), U ∩ V = ∅ := sorry

protected theorem disjoint_iff {α : Type u_1} {f : filter α} {g : filter α} : disjoint f g ↔ ∃ (U : set α), ∃ (H : U ∈ f), ∃ (V : set α), ∃ (H : V ∈ g), U ∩ V = ∅ :=
  iff.trans disjoint_iff inf_eq_bot_iff

theorem mem_iff_inf_principal_compl {α : Type u_1} {f : filter α} {s : set α} : s ∈ f ↔ f ⊓ principal (sᶜ) = ⊥ := sorry

theorem mem_iff_disjoint_principal_compl {α : Type u_1} {f : filter α} {s : set α} : s ∈ f ↔ disjoint f (principal (sᶜ)) :=
  iff.trans mem_iff_inf_principal_compl (iff.symm disjoint_iff)

theorem le_iff_forall_disjoint_principal_compl {α : Type u_1} {f : filter α} {g : filter α} : f ≤ g ↔ ∀ (V : set α), V ∈ g → disjoint f (principal (Vᶜ)) :=
  forall_congr fun (_x : set α) => forall_congr fun (_x_1 : _x ∈ g) => mem_iff_disjoint_principal_compl

theorem le_iff_forall_inf_principal_compl {α : Type u_1} {f : filter α} {g : filter α} : f ≤ g ↔ ∀ (V : set α), V ∈ g → f ⊓ principal (Vᶜ) = ⊥ :=
  forall_congr fun (_x : set α) => forall_congr fun (_x_1 : _x ∈ g) => mem_iff_inf_principal_compl

theorem inf_ne_bot_iff_frequently_left {α : Type u_1} {f : filter α} {g : filter α} : ne_bot (f ⊓ g) ↔ ∀ {p : α → Prop}, filter.eventually (fun (x : α) => p x) f → filter.frequently (fun (x : α) => p x) g := sorry

theorem inf_ne_bot_iff_frequently_right {α : Type u_1} {f : filter α} {g : filter α} : ne_bot (f ⊓ g) ↔ ∀ {p : α → Prop}, filter.eventually (fun (x : α) => p x) g → filter.frequently (fun (x : α) => p x) f := sorry

theorem has_basis.eq_binfi {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) : l = infi fun (i : ι) => infi fun (_x : p i) => principal (s i) := sorry

theorem has_basis.eq_infi {α : Type u_1} {ι : Type u_4} {l : filter α} {s : ι → set α} (h : has_basis l (fun (_x : ι) => True) s) : l = infi fun (i : ι) => principal (s i) := sorry

theorem has_basis_infi_principal {α : Type u_1} {ι : Type u_4} {s : ι → set α} (h : directed ge s) [Nonempty ι] : has_basis (infi fun (i : ι) => principal (s i)) (fun (_x : ι) => True) s := sorry

/-- If `s : ι → set α` is an indexed family of sets, then finite intersections of `s i` form a basis
of `⨅ i, 𝓟 (s i)`.  -/
theorem has_basis_infi_principal_finite {α : Type u_1} {ι : Type u_4} (s : ι → set α) : has_basis (infi fun (i : ι) => principal (s i)) (fun (t : set ι) => set.finite t)
  fun (t : set ι) => set.Inter fun (i : ι) => set.Inter fun (H : i ∈ t) => s i := sorry

theorem has_basis_binfi_principal {α : Type u_1} {β : Type u_2} {s : β → set α} {S : set β} (h : directed_on (s ⁻¹'o ge) S) (ne : set.nonempty S) : has_basis (infi fun (i : β) => infi fun (H : i ∈ S) => principal (s i)) (fun (i : β) => i ∈ S) s := sorry

theorem has_basis_binfi_principal' {α : Type u_1} {ι : Type u_4} {p : ι → Prop} {s : ι → set α} (h : ∀ (i : ι), p i → ∀ (j : ι), p j → ∃ (k : ι), ∃ (h : p k), s k ⊆ s i ∧ s k ⊆ s j) (ne : ∃ (i : ι), p i) : has_basis (infi fun (i : ι) => infi fun (h : p i) => principal (s i)) p s :=
  has_basis_binfi_principal h ne

theorem has_basis.map {α : Type u_1} {β : Type u_2} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (f : α → β) (hl : has_basis l p s) : has_basis (map f l) p fun (i : ι) => f '' s i := sorry

theorem has_basis.comap {α : Type u_1} {β : Type u_2} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (f : β → α) (hl : has_basis l p s) : has_basis (comap f l) p fun (i : ι) => f ⁻¹' s i := sorry

theorem comap_has_basis {α : Type u_1} {β : Type u_2} (f : α → β) (l : filter β) : has_basis (comap f l) (fun (s : set β) => s ∈ l) fun (s : set β) => f ⁻¹' s :=
  has_basis.mk fun (t : set α) => mem_comap_sets

theorem has_basis.prod_self {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (hl : has_basis l p s) : has_basis (filter.prod l l) p fun (i : ι) => set.prod (s i) (s i) := sorry

theorem mem_prod_self_iff {α : Type u_1} {l : filter α} {s : set (α × α)} : s ∈ filter.prod l l ↔ ∃ (t : set α), ∃ (H : t ∈ l), set.prod t t ⊆ s :=
  has_basis.mem_iff (has_basis.prod_self (basis_sets l))

theorem has_basis.sInter_sets {α : Type u_1} {ι : Type u_4} {l : filter α} {p : ι → Prop} {s : ι → set α} (h : has_basis l p s) : ⋂₀sets l = set.Inter fun (i : ι) => set.Inter fun (H : i ∈ set_of p) => s i := sorry

/-- `is_antimono_basis p s` means the image of `s` bounded by `p` is a filter basis
such that `s` is decreasing and `p` is increasing, ie `i ≤ j → p i → p j`. -/
structure is_antimono_basis {α : Type u_1} {ι : Type u_4} (p : ι → Prop) (s : ι → set α) [preorder ι]
extends is_basis p s
where
  decreasing : ∀ {i j : ι}, p i → p j → i ≤ j → s j ⊆ s i
  mono : monotone p

/-- We say that a filter `l` has a antimono basis `s : ι → set α` bounded by `p : ι → Prop`,
if `t ∈ l` if and only if `t` includes `s i` for some `i` such that `p i`,
and `s` is decreasing and `p` is increasing, ie `i ≤ j → p i → p j`. -/
structure has_antimono_basis {α : Type u_1} {ι : Type u_4} [preorder ι] [preorder ι] (l : filter α) (p : ι → Prop) (s : ι → set α)
extends has_basis l p s
where
  decreasing : ∀ {i j : ι}, p i → p j → i ≤ j → s j ⊆ s i
  mono : monotone p

theorem has_basis.tendsto_left_iff {α : Type u_1} {β : Type u_2} {ι : Type u_4} {la : filter α} {pa : ι → Prop} {sa : ι → set α} {lb : filter β} {f : α → β} (hla : has_basis la pa sa) : tendsto f la lb ↔ ∀ (t : set β), t ∈ lb → ∃ (i : ι), ∃ (hi : pa i), ∀ (x : α), x ∈ sa i → f x ∈ t := sorry

theorem has_basis.tendsto_right_iff {α : Type u_1} {β : Type u_2} {ι' : Type u_5} {la : filter α} {lb : filter β} {pb : ι' → Prop} {sb : ι' → set β} {f : α → β} (hlb : has_basis lb pb sb) : tendsto f la lb ↔ ∀ (i : ι'), pb i → filter.eventually (fun (x : α) => f x ∈ sb i) la := sorry

theorem has_basis.tendsto_iff {α : Type u_1} {β : Type u_2} {ι : Type u_4} {ι' : Type u_5} {la : filter α} {pa : ι → Prop} {sa : ι → set α} {lb : filter β} {pb : ι' → Prop} {sb : ι' → set β} {f : α → β} (hla : has_basis la pa sa) (hlb : has_basis lb pb sb) : tendsto f la lb ↔ ∀ (ib : ι'), pb ib → ∃ (ia : ι), ∃ (hia : pa ia), ∀ (x : α), x ∈ sa ia → f x ∈ sb ib := sorry

theorem tendsto.basis_left {α : Type u_1} {β : Type u_2} {ι : Type u_4} {la : filter α} {pa : ι → Prop} {sa : ι → set α} {lb : filter β} {f : α → β} (H : tendsto f la lb) (hla : has_basis la pa sa) (t : set β) : t ∈ lb → ∃ (i : ι), ∃ (hi : pa i), ∀ (x : α), x ∈ sa i → f x ∈ t :=
  iff.mp (has_basis.tendsto_left_iff hla) H

theorem tendsto.basis_right {α : Type u_1} {β : Type u_2} {ι' : Type u_5} {la : filter α} {lb : filter β} {pb : ι' → Prop} {sb : ι' → set β} {f : α → β} (H : tendsto f la lb) (hlb : has_basis lb pb sb) (i : ι') (hi : pb i) : filter.eventually (fun (x : α) => f x ∈ sb i) la :=
  iff.mp (has_basis.tendsto_right_iff hlb) H

theorem tendsto.basis_both {α : Type u_1} {β : Type u_2} {ι : Type u_4} {ι' : Type u_5} {la : filter α} {pa : ι → Prop} {sa : ι → set α} {lb : filter β} {pb : ι' → Prop} {sb : ι' → set β} {f : α → β} (H : tendsto f la lb) (hla : has_basis la pa sa) (hlb : has_basis lb pb sb) (ib : ι') (hib : pb ib) : ∃ (ia : ι), ∃ (hia : pa ia), ∀ (x : α), x ∈ sa ia → f x ∈ sb ib :=
  iff.mp (has_basis.tendsto_iff hla hlb) H

theorem has_basis.prod {α : Type u_1} {β : Type u_2} {ι : Type u_4} {ι' : Type u_5} {la : filter α} {pa : ι → Prop} {sa : ι → set α} {lb : filter β} {pb : ι' → Prop} {sb : ι' → set β} (hla : has_basis la pa sa) (hlb : has_basis lb pb sb) : has_basis (filter.prod la lb) (fun (i : ι × ι') => pa (prod.fst i) ∧ pb (prod.snd i))
  fun (i : ι × ι') => set.prod (sa (prod.fst i)) (sb (prod.snd i)) :=
  has_basis.inf (has_basis.comap prod.fst hla) (has_basis.comap prod.snd hlb)

theorem has_basis.prod' {α : Type u_1} {β : Type u_2} {la : filter α} {lb : filter β} {ι : Type u_3} {p : ι → Prop} {sa : ι → set α} {sb : ι → set β} (hla : has_basis la p sa) (hlb : has_basis lb p sb) (h_dir : ∀ {i j : ι}, p i → p j → ∃ (k : ι), p k ∧ sa k ⊆ sa i ∧ sb k ⊆ sb j) : has_basis (filter.prod la lb) p fun (i : ι) => set.prod (sa i) (sb i) := sorry

/-- `is_countably_generated f` means `f = generate s` for some countable `s`. -/
def is_countably_generated {α : Type u_1} (f : filter α) :=
  ∃ (s : set (set α)), set.countable s ∧ f = generate s

/-- `is_countable_basis p s` means the image of `s` bounded by `p` is a countable filter basis. -/
structure is_countable_basis {α : Type u_1} {ι : Type u_4} (p : ι → Prop) (s : ι → set α)
extends is_basis p s
where
  countable : set.countable (set_of p)

/-- We say that a filter `l` has a countable basis `s : ι → set α` bounded by `p : ι → Prop`,
if `t ∈ l` if and only if `t` includes `s i` for some `i` such that `p i`, and the set
defined by `p` is countable. -/
structure has_countable_basis {α : Type u_1} {ι : Type u_4} (l : filter α) (p : ι → Prop) (s : ι → set α)
extends has_basis l p s
where
  countable : set.countable (set_of p)

/-- A countable filter basis `B` on a type `α` is a nonempty countable collection of sets of `α`
such that the intersection of two elements of this collection contains some element
of the collection. -/
structure countable_filter_basis (α : Type u_6)
extends filter_basis α
where
  countable : set.countable (filter_basis.sets _to_filter_basis)

-- For illustration purposes, the countable filter basis defining (at_top : filter ℕ)

protected instance nat.inhabited_countable_filter_basis : Inhabited (countable_filter_basis ℕ) :=
  { default := countable_filter_basis.mk (filter_basis.mk (filter_basis.sets Inhabited.default) sorry sorry) sorry }

theorem antimono_seq_of_seq {α : Type u_1} (s : ℕ → set α) : ∃ (t : ℕ → set α),
  (∀ (i j : ℕ), i ≤ j → t j ⊆ t i) ∧ (infi fun (i : ℕ) => principal (s i)) = infi fun (i : ℕ) => principal (t i) := sorry

theorem countable_binfi_eq_infi_seq {α : Type u_1} {ι : Type u_4} [complete_lattice α] {B : set ι} (Bcbl : set.countable B) (Bne : set.nonempty B) (f : ι → α) : ∃ (x : ℕ → ι), (infi fun (t : ι) => infi fun (H : t ∈ B) => f t) = infi fun (i : ℕ) => f (x i) := sorry

theorem countable_binfi_eq_infi_seq' {α : Type u_1} {ι : Type u_4} [complete_lattice α] {B : set ι} (Bcbl : set.countable B) (f : ι → α) {i₀ : ι} (h : f i₀ = ⊤) : ∃ (x : ℕ → ι), (infi fun (t : ι) => infi fun (H : t ∈ B) => f t) = infi fun (i : ℕ) => f (x i) := sorry

theorem countable_binfi_principal_eq_seq_infi {α : Type u_1} {B : set (set α)} (Bcbl : set.countable B) : ∃ (x : ℕ → set α), (infi fun (t : set α) => infi fun (H : t ∈ B) => principal t) = infi fun (i : ℕ) => principal (x i) :=
  countable_binfi_eq_infi_seq' Bcbl principal principal_univ

namespace is_countably_generated

/-- A set generating a countably generated filter. -/
def generating_set {α : Type u_1} {f : filter α} (h : is_countably_generated f) : set (set α) :=
  classical.some h

theorem countable_generating_set {α : Type u_1} {f : filter α} (h : is_countably_generated f) : set.countable (generating_set h) :=
  and.left (classical.some_spec h)

theorem eq_generate {α : Type u_1} {f : filter α} (h : is_countably_generated f) : f = generate (generating_set h) :=
  and.right (classical.some_spec h)

/-- A countable filter basis for a countably generated filter. -/
def countable_filter_basis {α : Type u_1} {l : filter α} (h : is_countably_generated l) : countable_filter_basis α :=
  countable_filter_basis.mk (filter_basis.mk (filter_basis.sets (filter_basis.of_sets (generating_set h))) sorry sorry)
    sorry

theorem filter_basis_filter {α : Type u_1} {l : filter α} (h : is_countably_generated l) : filter_basis.filter (countable_filter_basis.to_filter_basis (countable_filter_basis h)) = l := sorry

theorem has_countable_basis {α : Type u_1} {l : filter α} (h : is_countably_generated l) : has_countable_basis l (fun (t : set (set α)) => set.finite t ∧ t ⊆ generating_set h) fun (t : set (set α)) => ⋂₀t := sorry

theorem exists_countable_infi_principal {α : Type u_1} {f : filter α} (h : is_countably_generated f) : ∃ (s : set (set α)), set.countable s ∧ f = infi fun (t : set α) => infi fun (H : t ∈ s) => principal t := sorry

theorem exists_seq {α : Type u_1} {f : filter α} (cblb : is_countably_generated f) : ∃ (x : ℕ → set α), f = infi fun (i : ℕ) => principal (x i) := sorry

/-- If `f` is countably generated and `f.has_basis p s`, then `f` admits a decreasing basis
enumerated by natural numbers such that all sets have the form `s i`. More precisely, there is a
sequence `i n` such that `p (i n)` for all `n` and `s (i n)` is a decreasing sequence of sets which
forms a basis of `f`-/
theorem exists_antimono_subbasis {α : Type u_1} {ι : Type u_4} {f : filter α} (cblb : is_countably_generated f) {p : ι → Prop} {s : ι → set α} (hs : has_basis f p s) : ∃ (x : ℕ → ι), (∀ (i : ℕ), p (x i)) ∧ has_antimono_basis f (fun (_x : ℕ) => True) fun (i : ℕ) => s (x i) := sorry

/-- A countably generated filter admits a basis formed by a monotonically decreasing sequence of
sets. -/
theorem exists_antimono_basis {α : Type u_1} {f : filter α} (cblb : is_countably_generated f) : ∃ (x : ℕ → set α), has_antimono_basis f (fun (_x : ℕ) => True) x := sorry

end is_countably_generated

theorem has_countable_basis.is_countably_generated {α : Type u_1} {ι : Type u_4} {f : filter α} {p : ι → Prop} {s : ι → set α} (h : has_countable_basis f p s) : is_countably_generated f :=
  Exists.intro (set_of fun (t : set α) => ∃ (i : ι), p i ∧ s i = t)
    { left := set.countable.image (has_countable_basis.countable h) s,
      right := has_basis.eq_generate (has_countable_basis.to_has_basis h) }

theorem is_countably_generated_seq {α : Type u_1} (x : ℕ → set α) : is_countably_generated (infi fun (i : ℕ) => principal (x i)) := sorry

theorem is_countably_generated_of_seq {α : Type u_1} {f : filter α} (h : ∃ (x : ℕ → set α), f = infi fun (i : ℕ) => principal (x i)) : is_countably_generated f := sorry

theorem is_countably_generated_binfi_principal {α : Type u_1} {B : set (set α)} (h : set.countable B) : is_countably_generated (infi fun (s : set α) => infi fun (H : s ∈ B) => principal s) :=
  is_countably_generated_of_seq (countable_binfi_principal_eq_seq_infi h)

theorem is_countably_generated_iff_exists_antimono_basis {α : Type u_1} {f : filter α} : is_countably_generated f ↔ ∃ (x : ℕ → set α), has_antimono_basis f (fun (_x : ℕ) => True) x := sorry

theorem is_countably_generated_principal {α : Type u_1} (s : set α) : is_countably_generated (principal s) := sorry

namespace is_countably_generated

theorem inf {α : Type u_1} {f : filter α} {g : filter α} (hf : is_countably_generated f) (hg : is_countably_generated g) : is_countably_generated (f ⊓ g) := sorry

theorem inf_principal {α : Type u_1} {f : filter α} (h : is_countably_generated f) (s : set α) : is_countably_generated (f ⊓ principal s) :=
  inf h (is_countably_generated_principal s)

theorem exists_antimono_seq' {α : Type u_1} {f : filter α} (cblb : is_countably_generated f) : ∃ (x : ℕ → set α), (∀ (i j : ℕ), i ≤ j → x j ⊆ x i) ∧ ∀ {s : set α}, s ∈ f ↔ ∃ (i : ℕ), x i ⊆ s := sorry

protected theorem comap {α : Type u_1} {β : Type u_2} {l : filter β} (h : is_countably_generated l) (f : α → β) : is_countably_generated (comap f l) := sorry


import tactic
-- the next two lines let us use the law of the excluded middle without trouble
noncomputable theory
open_locale classical

--BEGIN--

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

Delete the ``sorry,`` below and replace them with a legitimate proof.

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

-- men is type.
-- x : men means x is a man in the town
-- shaves x y is inhabited if x shaves y

variables (men : Type) (barber : men)
variable  (shaves : men → men → Prop)

example : ¬ (∀ x : men, shaves barber x ↔ ¬ shaves x x) :=
  begin
    sorry,
  end
--END--


import tactic
import .lesson5

noncomputable theory
open_locale classical

open PreHilbertPlane
open HilbertPlane

section plane_separation

variables {Ω : Type*} [HilbertPlane Ω]
variables {A B C : Ω}
variables {ℓ r s : Line Ω}

lemma point_neq_of_not_same_side (hA : ¬ A ∈ ℓ) (hB : ¬ B ∈ ℓ)
	(h : ¬ same_side ℓ A B) : A ≠ B :=
begin
	intro H, subst H,
	apply h same_side.refl,
end

lemma point_in_between_of_in_line_segment (ℓ : Line Ω) (hAℓ : A ∉ ℓ) (hBℓ : B ∉ ℓ):
	C ∈ pts (A⬝B) ∩ ℓ → A * C * B :=
begin
	intro h,
	simp only [set.mem_inter_eq, segments_are_symmetric] at h,
	rcases h with ⟨( h1| h1) | h2, hC⟩,
		tauto,
	{
		apply B11,
		obtain rfl | h2 := h2; tauto,
	},
end

lemma at_most_two_classes_of_noncollinear (hA : ¬ A ∈ ℓ) (hB : ¬ B ∈ ℓ) (hC : ¬ C ∈ ℓ)
		(hBneqC : B ≠ C) (hAB: ¬ same_side ℓ A B) (hAC: ¬ same_side ℓ A C)
		(h : ¬ collinear_triple A B C) : same_side ℓ B C :=
begin
	suffices : pts (B⬝C) ∩ ℓ = ∅,
	{
		right,
		exact this,
	},
	unfold same_side at hAB,
	push_neg at hAB,
	replace hAB := hAB.2,
	replace hAB : ∃ D : Ω, D ∈ pts (A⬝B) ∩ ℓ,
	{
		refine set.not_disjoint_iff.mp _,
		rwa set.disjoint_iff_inter_eq_empty,
	},
	cases hAB with D hD,
	have H' : A * D * B,
		apply point_in_between_of_in_line_segment ℓ; assumption,
	have H := HilbertPlane.B4 h hA hB hC hD.2,
	specialize H H',
	rcases H with ⟨ ⟨E, hE, h1⟩, hF⟩ | ⟨ ⟨E, hE, h1 ⟩, hF⟩,
	{
		push_neg at hF,
		by_contradiction hcontra,
		change pts (B⬝C) ∩ ℓ ≠ ∅ at hcontra,
		rw set.ne_empty_iff_nonempty at hcontra,
		cases hcontra with X hX,
		specialize hF X hX.2,
		apply hF,
		apply point_in_between_of_in_line_segment ℓ; assumption,
	},
	{
		exfalso,
		have hF' : ∃ E : Ω, (E ∈  ℓ) ∧ (A * E * C),
		{
			unfold same_side at hAC,
			push_neg at hAC,
			replace hAC := hAC.2,
			replace hAC : ∃ E : Ω, E ∈ pts (A⬝C) ∩ ℓ,
			{
				apply set.not_disjoint_iff.mp,
				rwa set.disjoint_iff_inter_eq_empty,
			},
			cases hAC with E hE,
			use E,
			split,
				exact hE.2,
				apply point_in_between_of_in_line_segment ℓ; assumption,
		},
		exact hA (false.rec (A ∈ ℓ) (hF hF')),
	},
end

lemma same_side.at_most_two_classes	(hA : ¬ A ∈ ℓ) (hB : ¬ B ∈ ℓ) (hC : ¬ C ∈ ℓ)
		(hBneqC : B ≠ C) (hAB: ¬ same_side ℓ A B) (hAC: ¬ same_side ℓ A C): same_side ℓ B C :=
begin
	by_cases collinear_triple A B C,
	{
		have hAneqB : A ≠ B := point_neq_of_not_same_side hA hB hAB,
		have hAneqC : A ≠ C := point_neq_of_not_same_side hA hC hAC,
		rw collinear_triple_of_equal A B C C B A at h,
		obtain ⟨D,E, ⟨ hDℓ, hEℓ, hCE, hDCE, hCBE, hEAB, hCAE⟩⟩ :=
			auxiliary_point ℓ h hC hB hA (ne.symm hBneqC) (ne.symm hAneqC) (ne.symm hAneqB),
		obtain rfl | BnE := em(B = E),
			apply same_side.symm hCE,
		have hAnE : ¬ same_side ℓ A E,
		{
			intro hAsE,
			apply hAC,
			apply same_side.trans hAsE,
			apply same_side.symm hCE,
		},
		have hBsE : same_side ℓ B E,
		{
			rw collinear_triple_of_equal E A B A B E at hEAB,
			apply at_most_two_classes_of_noncollinear hA hB hEℓ,
			repeat {assumption},
		},
		apply same_side.trans hBsE,
		apply same_side.symm hCE,
	},
	apply at_most_two_classes_of_noncollinear hA; assumption,
end

theorem plane_separation (ℓ : Line Ω):  ∃ S1 S2 : set Ω,
        S1 ∩ S2 = ∅ ∧ S1 ∪ S2 ∪ ℓ = {P : Ω | true} ∧
        (∀ A B : Ω, (A ∉ ℓ) → (B ∉ ℓ) →
			(( A ∈ S1 ∧ B ∈ S1) ∨ (A ∈ S2 ∧ B ∈ S2) ↔ pts (A⬝B) ∩ ℓ = ∅)) :=
begin
	have have_AB := same_side.at_least_two_classes ℓ,
	rcases have_AB with ⟨ C, D, hC, hD, H⟩,
	let S1 := {P : Ω | same_side ℓ P C},
	let S2 := {P : Ω | same_side ℓ P D},
	use [S1, S2],
	repeat {split},
	{ -- S1 ∩ S2 = ∅
		ext1,
		norm_num,
		intros h1 h2,
		apply H (same_side.trans (same_side.symm h1) h2),
	},
	{ -- S1 ∪ S2 ∪ ℓ = everything
		ext, norm_num,
		by_cases x ∈ ℓ,
		tauto,
		{
			left,
			by_cases hs : same_side ℓ C x,
				exact or.inl (same_side.symm hs),
			{
				right,
				obtain rfl | hXD := em(x = D),
					apply same_side.refl,
					apply same_side.at_most_two_classes; assumption,
			}
		}
	},
	{
		introv hA hB,
		split,
		{
			rintros (h | h);
			{ -- same_side A B
				have h1 : same_side ℓ A B,
				{
					apply same_side.trans h.1,
					apply same_side.symm h.2,
				},
				cases h1; tauto,
			},
		},
		{
			intro h,
			rw segments_are_symmetric at h,
			by_cases h1 : same_side ℓ A C,
			exact or.inl ⟨h1, same_side.trans (or.inr h) h1⟩,
			{
				right,
				have hAD : same_side ℓ A D,
				{
					replace h1 : ¬ same_side ℓ C A := λ x, h1 (same_side.symm x),
					obtain rfl | hAD := em(A = D),
						apply same_side.refl,
						apply same_side.at_most_two_classes; assumption,
				},
				exact ⟨ hAD, same_side.trans (or.inr h) hAD⟩,
			}
		},
	},
end

end plane_separation


/-
Copyright (c) 2022 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: María Inés de Frutos-Fernández
-/
import data.real.basic
import number_theory.function_field
import number_theory.number_field.basic
import ring_theory.tensor_product
import topology.metric_space.basic
import adeles_R

/-!
# The ring of adèles of a global field
We define the ring of finite adèles and the ring of adèles of a global field, both of which are
topological commutative rings.

## Main definitions
- `number_field.A_K_f` : The finite adèle ring of the number field `K`.
- `number_field.A_K`   : The adèle ring of the number field `K`.
- `function_field.A_F_f` : The finite adèle ring of the function field `F`.
- `function_field.A_F`   : The adèle ring of the function field `F`.

## References
* [J.W.S. Cassels, A. Frölich, *Algebraic Number Theory*][cassels1967algebraic]

## Tags
adèle ring, number field, function field
-/

noncomputable theory

open function is_dedekind_domain

open_locale tensor_product

namespace number_field
/-! ### The adèle ring of a number field
We define the (finite) adèle ring of a number field `K`, with its topological ring structure. -/
variables (K : Type) [field K] [number_field K]

/-- The finite adèle ring of `K`.-/
def A_K_f := finite_adele_ring' (ring_of_integers K) K
/-- The adèle ring of `K`.-/
def A_K := (A_K_f K) × (ℝ ⊗[ℚ] K)

-- We define the topological ring structure on `ℝ ⊗[ℚ] K`.
-- ℝ^n is a topological ring with the product topology.
variables (n : ℕ)
instance : ring (fin n → ℝ) := pi.ring
instance : topological_space (fin n → ℝ) := Pi.topological_space
instance : has_continuous_add (fin n → ℝ) := pi.has_continuous_add'
instance : has_continuous_mul (fin n → ℝ) := pi.has_continuous_mul'
instance : topological_semiring (fin n → ℝ) := topological_semiring.mk
instance : topological_ring (fin n → ℝ) := topological_ring.mk

open_locale big_operators
/-- `K` is isomorphic to `ℚ^(dim_ℚ(K))`. -/
def linear_equiv.Q_basis : (fin (finite_dimensional.finrank ℚ K) → ℚ) ≃ₗ[ℚ] K :=
linear_equiv.symm (basis.equiv_fun (finite_dimensional.fin_basis ℚ K))

/-- The natural linear map from `ℝ^n` to `ℝ ⊗[ℚ] ℚ^n`. -/
def linear_map.Rn_to_R_tensor_Qn (n : ℕ) : (fin n → ℝ) →ₗ[ℝ] (ℝ ⊗[ℚ] (fin n → ℚ)) :=
{ to_fun    := λ x, ∑ (m : fin n), tensor_product.mk _ _ _ (x m) (λ k : (fin n), (1 : ℚ)),
  map_add'  := λ x y, by simp only [map_add, tensor_product.mk_apply, pi.add_apply,
    linear_map.add_apply, finset.sum_add_distrib],
  map_smul' := λ r x, begin
    simp only [tensor_product.mk_apply, ring_hom.id_apply, pi.smul_apply, finset.smul_sum], refl,
  end, }

/-- The map `ℝ ⊗[ℚ] ℚ^(dim_ℚ(K)) → ℝ ⊗[ℚ] K` obtained as a base change of `linear_equiv.Q_basis`. -/
def linear_map.base_change :
(ℝ ⊗[ℚ] (fin (finite_dimensional.finrank ℚ K) → ℚ))  →ₗ[ℝ] (ℝ ⊗[ℚ] K) :=
linear_map.base_change ℝ (linear_equiv.Q_basis K).to_linear_map

/-- The resulting linear map from `ℝ^(dim_ℚ(K))` to `ℝ ⊗[ℚ] K`. -/
def linear_map.Rn_to_R_tensor_K : (fin (finite_dimensional.finrank ℚ K) → ℝ) →ₗ[ℝ] (ℝ ⊗[ℚ] K) :=
linear_map.comp (linear_map.base_change K) (linear_map.Rn_to_R_tensor_Qn _)

instance : comm_ring (A_K_f K) :=
finite_adele_ring'.comm_ring (ring_of_integers K) K
instance : comm_ring (A_K K) := prod.comm_ring
instance : topological_space (A_K_f K) :=
finite_adele_ring'.topological_space (ring_of_integers K) K
instance : topological_ring (A_K_f K) :=
finite_adele_ring'.topological_ring (ring_of_integers K) K
/-- The topological ring structuren on `ℝ ⊗[ℚ] K`. -/
def infinite_adeles.ring_topology : ring_topology (ℝ ⊗[ℚ] K) :=
ring_topology.coinduced (linear_map.Rn_to_R_tensor_K K)
instance : topological_space (ℝ ⊗[ℚ] K) := (infinite_adeles.ring_topology K).to_topological_space
instance : topological_ring (ℝ ⊗[ℚ] K) := (infinite_adeles.ring_topology K).to_topological_ring
instance : topological_space (A_K K) := prod.topological_space
instance : topological_ring (A_K K) := prod.topological_ring

/-- There exists a nonzero prime ideal of the ring of integers of a number field. -/
instance : inhabited (height_one_spectrum (ring_of_integers K)) :=
begin
  set M := classical.some(ideal.exists_maximal (ring_of_integers K)) with hM_def,
  set hM := classical.some_spec(ideal.exists_maximal (ring_of_integers K)),
  use [M, ideal.is_maximal.is_prime hM],
  { simp only [ideal.zero_eq_bot, ne.def],
    apply ring.ne_bot_of_is_maximal_of_not_is_field hM
      (number_field.ring_of_integers.not_is_field K), }
end

/-- The map from `K` to `A_K_f K` sending `k ∈ K ↦ (k)_v`. -/
def inj_K_f : K → A_K_f K := inj_K (ring_of_integers K) K

lemma inj_K_f.injective : injective (inj_K_f K) := inj_K.injective _ _

/-- The injection `inj_K_f` is a ring homomorphism from `K` to `A_K_f K`. Hence we can regard `K`
as a subring of `A_K_f K`. -/
def inj_K_f.ring_hom : ring_hom K (A_K_f K) := inj_K.ring_hom (ring_of_integers K) K

lemma inj_K_f.ring_hom.v_comp (v : height_one_spectrum (ring_of_integers K)) (k : K) :
  ((inj_K_f.ring_hom K) k).val v = (coe : K → (v.adic_completion K)) k := rfl

/-- The map from `K` to `A_K K` sending `k ∈ K ↦ ((k)_v, 1 ⊗ k)`. -/
def inj_K : K → A_K K := λ x, ⟨inj_K_f K x, algebra.tensor_product.include_right x⟩

lemma inj_K.injective : injective (inj_K K) :=
begin
  intros x y hxy,
  rw [inj_K, prod.mk.inj_iff] at hxy,
  exact inj_K_f.injective K hxy.1,
end

/-- The injection `inj_K` is a ring homomorphism from `K` to `A_K K`. Hence we can regard `K`
as a subring of `A_K K`. -/
def inj_K.ring_hom : ring_hom K (A_K K) :=
ring_hom.prod (inj_K_f.ring_hom K) (@algebra.tensor_product.include_right ℚ _ ℝ _ _ K _ _ )

end number_field

namespace function_field
/-! ### The adèle ring of a function field
We define the (finite) adèle ring of a function field `F`, with its topological ring structure. -/
variables (k F : Type) [field k] [field F] [algebra (polynomial k) F] [algebra (ratfunc k) F]
  [function_field k F]
  [is_scalar_tower (polynomial k) (ratfunc k) F]
  [is_separable (ratfunc k) F] [decidable_eq (ratfunc k)]

instance : algebra (ratfunc k) (Fqt_infty k) :=
begin
  apply ring_hom.to_algebra (@uniform_space.completion.coe_ring_hom (ratfunc k) _
    (function_field.infty_valued_Fqt k).to_uniform_space _
    (function_field.infty_valued_Fqt k).to_uniform_add_group),
end

/-- The finite adèle ring of `F`.-/
def A_F_f := finite_adele_ring' (ring_of_integers k F) F
/-- The finite adèle ring of `F`.-/
def A_F := (A_F_f k F) × ((Fqt_infty k) ⊗[ratfunc k] F)

open_locale big_operators

/-- `F` is isomorphic to `k(t)^(dim_(F_q(t))(F))`. -/
def linear_equiv.kt_basis :
  (fin (finite_dimensional.finrank (ratfunc k) F) → (ratfunc k)) ≃ₗ[(ratfunc k)] F :=
linear_equiv.symm (basis.equiv_fun (finite_dimensional.fin_basis (ratfunc k) F))

/-- The natural linear map from `k((t⁻¹))^n` to `k((t⁻¹)) ⊗[k(t)] k(t)^n`. -/
def linear_map.Fqt_inftyn_to_Fqt_infty_tensor_ktn (n : ℕ):
  (fin n → (Fqt_infty k)) →ₗ[(Fqt_infty k)]
    ((Fqt_infty k) ⊗[(ratfunc k)] (fin n→ (ratfunc k))) :=
{ to_fun    := λ x, ∑ (m : fin n), tensor_product.mk _ _ _ (x m)
    (λ m : (fin n), (1 : (ratfunc k))),
  map_add'  := λ x y, by simp only [map_add, tensor_product.mk_apply, pi.add_apply,
    linear_map.add_apply, finset.sum_add_distrib],
  map_smul' := λ r x, begin
    simp only [tensor_product.mk_apply, ring_hom.id_apply, pi.smul_apply, finset.smul_sum], refl,
end, }

/-- The linear map from `k((t⁻¹)) ⊗[k(t)] k(t)^(dim_(F_q(t))(F))` to `k((t⁻¹)) ⊗[k(t)] F`
obtained as a base change of `linear_equiv.kt_basis`. -/
def linear_map.base_change : ((Fqt_infty k) ⊗[ratfunc k]
  (fin (finite_dimensional.finrank (ratfunc k) F) → ratfunc k)) →ₗ[Fqt_infty k]
    ((Fqt_infty k) ⊗[ratfunc k] F) :=
linear_map.base_change (Fqt_infty k) (linear_equiv.kt_basis k F).to_linear_map

/-- The resulting linear map from `k((t⁻¹))^(dim_(F_q(t))(F))` to `k((t⁻¹)) ⊗[k(t)] F`. -/
def linear_map.Fqt_inftyn_to_Fqt_infty_tensor_F : (fin (finite_dimensional.finrank (ratfunc k) F) →
  (Fqt_infty k)) →ₗ[Fqt_infty k] ((Fqt_infty k) ⊗[ratfunc k] F) :=
linear_map.comp (linear_map.base_change k F) (linear_map.Fqt_inftyn_to_Fqt_infty_tensor_ktn k _)

instance : comm_ring (A_F_f k F) := finite_adele_ring'.comm_ring (ring_of_integers k F) F
instance : comm_ring (A_F k F) := prod.comm_ring
instance : topological_space (A_F_f k F) :=
finite_adele_ring'.topological_space (ring_of_integers k F) F
instance : topological_ring (A_F_f k F) :=
finite_adele_ring'.topological_ring (ring_of_integers k F) F
/-- The topological ring structure on the infinite places of `F`. -/
def infinite_adeles.ring_topology : ring_topology ((Fqt_infty k) ⊗[ratfunc k] F) :=
ring_topology.coinduced (linear_map.Fqt_inftyn_to_Fqt_infty_tensor_F k F)
instance : topological_space ((Fqt_infty k) ⊗[ratfunc k] F) :=
(infinite_adeles.ring_topology k F).to_topological_space
instance : topological_ring ((Fqt_infty k) ⊗[ratfunc k] F) :=
(infinite_adeles.ring_topology k F).to_topological_ring
instance : topological_space (A_F k F) := prod.topological_space
instance : topological_ring (A_F k F) := prod.topological_ring

end function_field


import data.nat.sqrt data.nat.parity data.set.finite

/-! # IMO 2016 A5 -/

namespace IMOSL
namespace IMO2016A5

/-- Final solution, part 1 -/
theorem final_solution_part1 (n : ℕ) :
  ∃ a b : ℕ, 0 < b ∧ b ≤ nat.sqrt n + 1 ∧ b ^ 2 * n ≤ a ^ 2 ∧ a ^ 2 ≤ b ^ 2 * (n + 1) :=
begin
  obtain ⟨r, s, h, rfl⟩ : ∃ r s : ℕ, n ≤ r * r + r + r ∧ r ^ 2 + s = n :=
    ⟨n.sqrt, n - n.sqrt ^ 2, n.sqrt_le_add, nat.add_sub_of_le n.sqrt_le'⟩,
  rw [← sq, add_assoc, add_le_add_iff_left] at h,
  rw nat.sqrt_add_eq' r h; rcases s.even_or_odd with ⟨k, rfl⟩ | ⟨k, rfl⟩,

  ---- Case 1: `s` is even
  { rw [← two_mul, ← two_mul, mul_le_mul_left (two_pos : 0 < 2)] at h,
    rcases r.eq_zero_or_pos with rfl | h0,
    refine ⟨0, 1, one_pos, le_refl _, _, zero_le _⟩,
    rw le_zero_iff at h,
    rw [h, one_pow, one_mul, sq, zero_mul, add_zero],
    refine ⟨r ^ 2 + k, r, h0, le_self_add, _⟩,
    rw [mul_add_one, add_sq, sq (r ^ 2), mul_right_comm, ← add_mul, ← two_mul, mul_comm],
    exact ⟨le_self_add, add_le_add_left (pow_le_pow_of_le_left' h 2) _⟩ },

  ---- Case 2: `s` is odd
  { rw [nat.succ_le_iff, ← two_mul, mul_lt_mul_left (two_pos : 0 < 2)] at h,
    refine ⟨r ^ 2 + 1 + 2 * k + (r - k), r + 1, r.succ_pos, le_refl _, _⟩,
    rw [add_sq', one_pow, mul_one, add_sq, sq (r ^ 2 + 1 + _), mul_right_comm,
        ← add_mul, add_assoc _ (2 * k), ← mul_add, nat.add_sub_of_le (le_of_lt h),
        add_comm (2 * k), ← add_assoc, mul_add_one],
    refine ⟨le_self_add, add_le_add_left _ _⟩,
    rw add_assoc; exact le_trans (pow_le_pow_of_le_left' (nat.sub_le r k) 2) le_self_add }
end

/-- Final solution, part 2 -/
theorem final_solution_part2 : {n : ℕ | ¬∃ a b : ℕ, 0 < b ∧ b ≤ nat.sqrt n ∧
  b ^ 2 * n ≤ a ^ 2 ∧ a ^ 2 ≤ b ^ 2 * (n + 1)}.infinite :=
begin
  ---- Pick `n = y^2 + 1`, `y > 0` so that `n.sqrt = y`
  have h : function.injective (λ x : ℕ, x.succ ^ 2 + 1) :=
    λ a b h, nat.succ_injective (nat.pow_left_injective one_le_two (add_left_injective 1 h)),
  refine set.infinite_of_injective_forall_mem h (λ x h0, _),
  rcases h0 with ⟨a, b, h0, h1, h2, h3⟩,
  rw nat.sqrt_add_eq' _ (le_add_right (nat.succ_le_succ (zero_le x))) at h1,
  generalize_hyp : x.succ = y at h1 h2 h3; clear h x,

  ---- Get the contradiction
  rw [mul_add_one, ← mul_pow] at h2,
  replace h2 := lt_of_lt_of_le (lt_add_of_pos_right _ (pow_pos h0 2)) h2,
  rw [nat.pow_lt_iff_lt_left one_le_two, ← nat.add_one_le_iff] at h2,
  revert h3; rw [imp_false, not_le, add_assoc, ← bit0, mul_add, ← mul_pow],
  refine lt_of_lt_of_le _ (pow_le_pow_of_le_left' h2 2),
  rw [add_sq, one_pow, nat.lt_succ_iff, add_le_add_iff_left, mul_one, sq, mul_comm],
  exact mul_le_mul_left' (mul_le_mul_left' h1 b) 2
end

end IMO2016A5
end IMOSL