/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl
-/
import algebra.big_operators.intervals
import algebra.big_operators.nat_antidiagonal
import data.equiv.encodable.lattice
import topology.algebra.mul_action
import topology.algebra.ordered.monotone_convergence
import topology.instances.real

/-!
# Infinite sum over a topological monoid

This sum is known as unconditionally convergent, as it sums to the same value under all possible
permutations. For Euclidean spaces (finite dimensional Banach spaces) this is equivalent to absolute
convergence.

Note: There are summable sequences which are not unconditionally convergent! The other way holds
generally, see `has_sum.tendsto_sum_nat`.

## References

* Bourbaki: General Topology (1995), Chapter 3 §5 (Infinite sums in commutative groups)

-/

noncomputable theory
open finset filter function classical
open_locale topological_space classical big_operators nnreal

variables {α : Type*} {β : Type*} {γ : Type*} {δ : Type*}

section has_sum
variables [add_comm_monoid α] [topological_space α]

/-- Infinite sum on a topological monoid

The `at_top` filter on `finset β` is the limit of all finite sets towards the entire type. So we sum
up bigger and bigger sets. This sum operation is invariant under reordering. In particular,
the function `ℕ → ℝ` sending `n` to `(-1)^n / (n+1)` does not have a
sum for this definition, but a series which is absolutely convergent will have the correct sum.

This is based on Mario Carneiro's
[infinite sum `df-tsms` in Metamath](http://us.metamath.org/mpeuni/df-tsms.html).

For the definition or many statements, `α` does not need to be a topological monoid. We only add
this assumption later, for the lemmas where it is relevant.
-/
def has_sum (f : β → α) (a : α) : Prop := tendsto (λs:finset β, ∑ b in s, f b) at_top (𝓝 a)

/-- `summable f` means that `f` has some (infinite) sum. Use `tsum` to get the value. -/
def summable (f : β → α) : Prop := ∃a, has_sum f a

/-- `∑' i, f i` is the sum of `f` it exists, or 0 otherwise -/
@[irreducible] def tsum {β} (f : β → α) := if h : summable f then classical.some h else 0

-- see Note [operator precedence of big operators]
notation `∑'` binders `, ` r:(scoped:67 f, tsum f) := r

variables {f g : β → α} {a b : α} {s : finset β}

lemma summable.has_sum (ha : summable f) : has_sum f (∑'b, f b) :=
by simp [ha, tsum]; exact some_spec ha

lemma has_sum.summable (h : has_sum f a) : summable f := ⟨a, h⟩

/-- Constant zero function has sum `0` -/
lemma has_sum_zero : has_sum (λb, 0 : β → α) 0 :=
by simp [has_sum, tendsto_const_nhds]

lemma has_sum_empty [is_empty β] : has_sum f 0 :=
by convert has_sum_zero

lemma summable_zero : summable (λb, 0 : β → α) := has_sum_zero.summable

lemma summable_empty [is_empty β] : summable f := has_sum_empty.summable

lemma tsum_eq_zero_of_not_summable (h : ¬ summable f) : ∑'b, f b = 0 :=
by simp [tsum, h]

lemma summable_congr (hfg : ∀b, f b = g b) :
  summable f ↔ summable g :=
iff_of_eq (congr_arg summable $ funext hfg)

lemma summable.congr (hf : summable f) (hfg : ∀b, f b = g b) :
  summable g :=
(summable_congr hfg).mp hf

lemma has_sum.has_sum_of_sum_eq {g : γ → α}
  (h_eq : ∀u:finset γ, ∃v:finset β, ∀v', v ⊆ v' → ∃u', u ⊆ u' ∧ ∑ x in u', g x = ∑ b in v', f b)
  (hf : has_sum g a) :
  has_sum f a :=
le_trans (map_at_top_finset_sum_le_of_sum_eq h_eq) hf

lemma has_sum_iff_has_sum {g : γ → α}
  (h₁ : ∀u:finset γ, ∃v:finset β, ∀v', v ⊆ v' → ∃u', u ⊆ u' ∧ ∑ x in u', g x = ∑ b in v', f b)
  (h₂ : ∀v:finset β, ∃u:finset γ, ∀u', u ⊆ u' → ∃v', v ⊆ v' ∧ ∑ b in v', f b = ∑ x in u', g x) :
  has_sum f a ↔ has_sum g a :=
⟨has_sum.has_sum_of_sum_eq h₂, has_sum.has_sum_of_sum_eq h₁⟩

lemma function.injective.has_sum_iff {g : γ → β} (hg : injective g)
  (hf : ∀ x ∉ set.range g, f x = 0) :
  has_sum (f ∘ g) a ↔ has_sum f a :=
by simp only [has_sum, tendsto, hg.map_at_top_finset_sum_eq hf]

lemma function.injective.summable_iff {g : γ → β} (hg : injective g)
  (hf : ∀ x ∉ set.range g, f x = 0) :
  summable (f ∘ g) ↔ summable f :=
exists_congr $ λ _, hg.has_sum_iff hf

lemma has_sum_subtype_iff_of_support_subset {s : set β} (hf : support f ⊆ s) :
  has_sum (f ∘ coe : s → α) a ↔ has_sum f a :=
subtype.coe_injective.has_sum_iff $ by simpa using support_subset_iff'.1 hf

lemma has_sum_subtype_iff_indicator {s : set β} :
  has_sum (f ∘ coe : s → α) a ↔ has_sum (s.indicator f) a :=
by rw [← set.indicator_range_comp, subtype.range_coe,
  has_sum_subtype_iff_of_support_subset set.support_indicator_subset]

@[simp] lemma has_sum_subtype_support : has_sum (f ∘ coe : support f → α) a ↔ has_sum f a :=
has_sum_subtype_iff_of_support_subset $ set.subset.refl _

lemma has_sum_fintype [fintype β] (f : β → α) : has_sum f (∑ b, f b) :=
order_top.tendsto_at_top_nhds _

protected lemma finset.has_sum (s : finset β) (f : β → α) :
  has_sum (f ∘ coe : (↑s : set β) → α) (∑ b in s, f b) :=
by { rw ← sum_attach, exact has_sum_fintype _ }

protected lemma finset.summable (s : finset β) (f : β → α) :
  summable (f ∘ coe : (↑s : set β) → α) :=
(s.has_sum f).summable

protected lemma set.finite.summable {s : set β} (hs : s.finite) (f : β → α) :
  summable (f ∘ coe : s → α) :=
by convert hs.to_finset.summable f; simp only [hs.coe_to_finset]

/-- If a function `f` vanishes outside of a finite set `s`, then it `has_sum` `∑ b in s, f b`. -/
lemma has_sum_sum_of_ne_finset_zero (hf : ∀b∉s, f b = 0) : has_sum f (∑ b in s, f b) :=
(has_sum_subtype_iff_of_support_subset $ support_subset_iff'.2 hf).1 $ s.has_sum f

lemma summable_of_ne_finset_zero (hf : ∀b∉s, f b = 0) : summable f :=
(has_sum_sum_of_ne_finset_zero hf).summable

lemma has_sum_single {f : β → α} (b : β) (hf : ∀b' ≠ b, f b' = 0) :
  has_sum f (f b) :=
suffices has_sum f (∑ b' in {b}, f b'),
  by simpa using this,
has_sum_sum_of_ne_finset_zero $ by simpa [hf]

lemma has_sum_ite_eq (b : β) [decidable_pred (= b)] (a : α) :
  has_sum (λb', if b' = b then a else 0) a :=
begin
  convert has_sum_single b _,
  { exact (if_pos rfl).symm },
  assume b' hb',
  exact if_neg hb'
end

lemma equiv.has_sum_iff (e : γ ≃ β) :
  has_sum (f ∘ e) a ↔ has_sum f a :=
e.injective.has_sum_iff $ by simp

lemma function.injective.has_sum_range_iff {g : γ → β} (hg : injective g) :
  has_sum (λ x : set.range g, f x) a ↔ has_sum (f ∘ g) a :=
(equiv.of_injective g hg).has_sum_iff.symm

lemma equiv.summable_iff (e : γ ≃ β) :
  summable (f ∘ e) ↔ summable f :=
exists_congr $ λ a, e.has_sum_iff

lemma summable.prod_symm {f : β × γ → α} (hf : summable f) : summable (λ p : γ × β, f p.swap) :=
(equiv.prod_comm γ β).summable_iff.2 hf

lemma equiv.has_sum_iff_of_support {g : γ → α} (e : support f ≃ support g)
  (he : ∀ x : support f, g (e x) = f x) :
  has_sum f a ↔ has_sum g a :=
have (g ∘ coe) ∘ e = f ∘ coe, from funext he,
by rw [← has_sum_subtype_support, ← this, e.has_sum_iff, has_sum_subtype_support]

lemma has_sum_iff_has_sum_of_ne_zero_bij {g : γ → α} (i : support g → β)
  (hi : ∀ ⦃x y⦄, i x = i y → (x : γ) = y)
  (hf : support f ⊆ set.range i) (hfg : ∀ x, f (i x) = g x) :
  has_sum f a ↔ has_sum g a :=
iff.symm $ equiv.has_sum_iff_of_support
  (equiv.of_bijective (λ x, ⟨i x, λ hx, x.coe_prop $ hfg x ▸ hx⟩)
    ⟨λ x y h, subtype.ext $ hi $ subtype.ext_iff.1 h,
      λ y, (hf y.coe_prop).imp $ λ x hx, subtype.ext hx⟩)
  hfg

lemma equiv.summable_iff_of_support {g : γ → α} (e : support f ≃ support g)
  (he : ∀ x : support f, g (e x) = f x) :
  summable f ↔ summable g :=
exists_congr $ λ _, e.has_sum_iff_of_support he

protected lemma has_sum.map [add_comm_monoid γ] [topological_space γ] (hf : has_sum f a)
  (g : α →+ γ) (hg : continuous g) :
  has_sum (g ∘ f) (g a) :=
have g ∘ (λs:finset β, ∑ b in s, f b) = (λs:finset β, ∑ b in s, g (f b)),
  from funext $ g.map_sum _,
show tendsto (λs:finset β, ∑ b in s, g (f b)) at_top (𝓝 (g a)),
  from this ▸ (hg.tendsto a).comp hf

protected lemma summable.map [add_comm_monoid γ] [topological_space γ] (hf : summable f)
  (g : α →+ γ) (hg : continuous g) :
  summable (g ∘ f) :=
(hf.has_sum.map g hg).summable

/-- If `f : ℕ → α` has sum `a`, then the partial sums `∑_{i=0}^{n-1} f i` converge to `a`. -/
lemma has_sum.tendsto_sum_nat {f : ℕ → α} (h : has_sum f a) :
  tendsto (λn:ℕ, ∑ i in range n, f i) at_top (𝓝 a) :=
h.comp tendsto_finset_range

lemma has_sum.unique {a₁ a₂ : α} [t2_space α] : has_sum f a₁ → has_sum f a₂ → a₁ = a₂ :=
tendsto_nhds_unique

lemma summable.has_sum_iff_tendsto_nat [t2_space α] {f : ℕ → α} {a : α} (hf : summable f) :
  has_sum f a ↔ tendsto (λn:ℕ, ∑ i in range n, f i) at_top (𝓝 a) :=
begin
  refine ⟨λ h, h.tendsto_sum_nat, λ h, _⟩,
  rw tendsto_nhds_unique h hf.has_sum.tendsto_sum_nat,
  exact hf.has_sum
end

lemma equiv.summable_iff_of_has_sum_iff {α' : Type*} [add_comm_monoid α']
  [topological_space α'] (e : α' ≃ α) {f : β → α} {g : γ → α'}
  (he : ∀ {a}, has_sum f (e a) ↔ has_sum g a) :
  summable f ↔ summable g :=
⟨λ ⟨a, ha⟩, ⟨e.symm a, he.1 $ by rwa [e.apply_symm_apply]⟩, λ ⟨a, ha⟩, ⟨e a, he.2 ha⟩⟩

variable [has_continuous_add α]

lemma has_sum.add (hf : has_sum f a) (hg : has_sum g b) : has_sum (λb, f b + g b) (a + b) :=
by simp only [has_sum, sum_add_distrib]; exact hf.add hg

lemma summable.add (hf : summable f) (hg : summable g) : summable (λb, f b + g b) :=
(hf.has_sum.add hg.has_sum).summable

lemma has_sum_sum {f : γ → β → α} {a : γ → α} {s : finset γ} :
  (∀i∈s, has_sum (f i) (a i)) → has_sum (λb, ∑ i in s, f i b) (∑ i in s, a i) :=
finset.induction_on s (by simp only [has_sum_zero, sum_empty, forall_true_iff])
  (by simp only [has_sum.add, sum_insert, mem_insert, forall_eq_or_imp,
        forall_2_true_iff, not_false_iff, forall_true_iff] {contextual := tt})

lemma summable_sum {f : γ → β → α} {s : finset γ} (hf : ∀i∈s, summable (f i)) :
  summable (λb, ∑ i in s, f i b) :=
(has_sum_sum $ assume i hi, (hf i hi).has_sum).summable

lemma has_sum.add_disjoint {s t : set β} (hs : disjoint s t)
  (ha : has_sum (f ∘ coe : s → α) a) (hb : has_sum (f ∘ coe : t → α) b) :
  has_sum (f ∘ coe : s ∪ t → α) (a + b) :=
begin
  rw has_sum_subtype_iff_indicator at *,
  rw set.indicator_union_of_disjoint hs,
  exact ha.add hb
end

lemma has_sum.add_is_compl {s t : set β} (hs : is_compl s t)
  (ha : has_sum (f ∘ coe : s → α) a) (hb : has_sum (f ∘ coe : t → α) b) :
  has_sum f (a + b) :=
by simpa [← hs.compl_eq]
  using (has_sum_subtype_iff_indicator.1 ha).add (has_sum_subtype_iff_indicator.1 hb)

lemma has_sum.add_compl {s : set β} (ha : has_sum (f ∘ coe : s → α) a)
  (hb : has_sum (f ∘ coe : sᶜ → α) b) :
  has_sum f (a + b) :=
ha.add_is_compl is_compl_compl hb

lemma summable.add_compl {s : set β} (hs : summable (f ∘ coe : s → α))
  (hsc : summable (f ∘ coe : sᶜ → α)) :
  summable f :=
(hs.has_sum.add_compl hsc.has_sum).summable

lemma has_sum.compl_add {s : set β} (ha : has_sum (f ∘ coe : sᶜ → α) a)
  (hb : has_sum (f ∘ coe : s → α) b) :
  has_sum f (a + b) :=
ha.add_is_compl is_compl_compl.symm hb

lemma has_sum.even_add_odd {f : ℕ → α} (he : has_sum (λ k, f (2 * k)) a)
  (ho : has_sum (λ k, f (2 * k + 1)) b) :
  has_sum f (a + b) :=
begin
  have := mul_right_injective₀ (@two_ne_zero ℕ _ _),
  replace he := this.has_sum_range_iff.2 he,
  replace ho := ((add_left_injective 1).comp this).has_sum_range_iff.2 ho,
  refine he.add_is_compl _ ho,
  simpa [(∘)] using nat.is_compl_even_odd
end

lemma summable.compl_add {s : set β} (hs : summable (f ∘ coe : sᶜ → α))
  (hsc : summable (f ∘ coe : s → α)) :
  summable f :=
(hs.has_sum.compl_add hsc.has_sum).summable

lemma summable.even_add_odd {f : ℕ → α} (he : summable (λ k, f (2 * k)))
  (ho : summable (λ k, f (2 * k + 1))) :
  summable f :=
(he.has_sum.even_add_odd ho.has_sum).summable

lemma has_sum.sigma [regular_space α] {γ : β → Type*} {f : (Σ b:β, γ b) → α} {g : β → α} {a : α}
  (ha : has_sum f a) (hf : ∀b, has_sum (λc, f ⟨b, c⟩) (g b)) : has_sum g a :=
begin
  refine (at_top_basis.tendsto_iff (closed_nhds_basis a)).mpr _,
  rintros s ⟨hs, hsc⟩,
  rcases mem_at_top_sets.mp (ha hs) with ⟨u, hu⟩,
  use [u.image sigma.fst, trivial],
  intros bs hbs,
  simp only [set.mem_preimage, ge_iff_le, finset.le_iff_subset] at hu,
  have : tendsto (λ t : finset (Σ b, γ b), ∑ p in t.filter (λ p, p.1 ∈ bs), f p)
    at_top (𝓝 $ ∑ b in bs, g b),
  { simp only [← sigma_preimage_mk, sum_sigma],
    refine tendsto_finset_sum _ (λ b hb, _),
    change tendsto (λ t, (λ t, ∑ s in t, f ⟨b, s⟩) (preimage t (sigma.mk b) _)) at_top (𝓝 (g b)),
    exact tendsto.comp (hf b) (tendsto_finset_preimage_at_top_at_top _) },
  refine hsc.mem_of_tendsto this (eventually_at_top.2 ⟨u, λ t ht, hu _ (λ x hx, _)⟩),
  exact mem_filter.2 ⟨ht hx, hbs $ mem_image_of_mem _ hx⟩
end

/-- If a series `f` on `β × γ` has sum `a` and for each `b` the restriction of `f` to `{b} × γ`
has sum `g b`, then the series `g` has sum `a`. -/
lemma has_sum.prod_fiberwise [regular_space α] {f : β × γ → α} {g : β → α} {a : α}
  (ha : has_sum f a) (hf : ∀b, has_sum (λc, f (b, c)) (g b)) :
  has_sum g a :=
has_sum.sigma ((equiv.sigma_equiv_prod β γ).has_sum_iff.2 ha) hf

lemma summable.sigma' [regular_space α] {γ : β → Type*} {f : (Σb:β, γ b) → α}
  (ha : summable f) (hf : ∀b, summable (λc, f ⟨b, c⟩)) :
  summable (λb, ∑'c, f ⟨b, c⟩) :=
(ha.has_sum.sigma (assume b, (hf b).has_sum)).summable

lemma has_sum.sigma_of_has_sum [regular_space α] {γ : β → Type*} {f : (Σ b:β, γ b) → α} {g : β → α}
  {a : α} (ha : has_sum g a) (hf : ∀b, has_sum (λc, f ⟨b, c⟩) (g b)) (hf' : summable f) :
  has_sum f a :=
by simpa [(hf'.has_sum.sigma hf).unique ha] using hf'.has_sum

end has_sum

section tsum
variables [add_comm_monoid α] [topological_space α] [t2_space α]
variables {f g : β → α} {a a₁ a₂ : α}

lemma has_sum.tsum_eq (ha : has_sum f a) : ∑'b, f b = a :=
(summable.has_sum ⟨a, ha⟩).unique ha

lemma summable.has_sum_iff (h : summable f) : has_sum f a ↔ ∑'b, f b = a :=
iff.intro has_sum.tsum_eq (assume eq, eq ▸ h.has_sum)

@[simp] lemma tsum_zero : ∑'b:β, (0:α) = 0 := has_sum_zero.tsum_eq

@[simp] lemma tsum_empty [is_empty β] : ∑'b, f b = 0 := has_sum_empty.tsum_eq

lemma tsum_eq_sum {f : β → α} {s : finset β} (hf : ∀b∉s, f b = 0)  :
  ∑' b, f b = ∑ b in s, f b :=
(has_sum_sum_of_ne_finset_zero hf).tsum_eq

lemma tsum_congr {α β : Type*} [add_comm_monoid α] [topological_space α]
  {f g : β → α} (hfg : ∀ b, f b = g b) : ∑' b, f b = ∑' b, g b :=
congr_arg tsum (funext hfg)

lemma tsum_fintype [fintype β] (f : β → α) : ∑'b, f b = ∑ b, f b :=
(has_sum_fintype f).tsum_eq

lemma tsum_bool (f : bool → α) : ∑' i : bool, f i = f false + f true :=
by { rw [tsum_fintype, finset.sum_eq_add]; simp }

@[simp] lemma finset.tsum_subtype (s : finset β) (f : β → α) :
  ∑' x : {x // x ∈ s}, f x = ∑ x in s, f x :=
(s.has_sum f).tsum_eq

@[simp] lemma finset.tsum_subtype' (s : finset β) (f : β → α) :
  ∑' x : (s : set β), f x = ∑ x in s, f x :=
s.tsum_subtype f

lemma tsum_eq_single {f : β → α} (b : β) (hf : ∀b' ≠ b, f b' = 0)  :
  ∑'b, f b = f b :=
(has_sum_single b hf).tsum_eq

@[simp] lemma tsum_ite_eq (b : β) [decidable_pred (= b)] (a : α) :
  ∑' b', (if b' = b then a else 0) = a :=
(has_sum_ite_eq b a).tsum_eq

lemma tsum_dite_right (P : Prop) [decidable P] (x : β → ¬ P → α) :
  ∑' (b : β), (if h : P then (0 : α) else x b h) = if h : P then (0 : α) else ∑' (b : β), x b h :=
by by_cases hP : P; simp [hP]

lemma tsum_dite_left (P : Prop) [decidable P] (x : β → P → α) :
  ∑' (b : β), (if h : P then x b h else 0) = if h : P then (∑' (b : β), x b h) else 0 :=
by by_cases hP : P; simp [hP]

lemma equiv.tsum_eq_tsum_of_has_sum_iff_has_sum {α' : Type*} [add_comm_monoid α']
  [topological_space α'] (e : α' ≃ α) (h0 : e 0 = 0) {f : β → α} {g : γ → α'}
  (h : ∀ {a}, has_sum f (e a) ↔ has_sum g a) :
  ∑' b, f b = e (∑' c, g c) :=
by_cases
  (assume : summable g, (h.mpr this.has_sum).tsum_eq)
  (assume hg : ¬ summable g,
    have hf : ¬ summable f, from mt (e.summable_iff_of_has_sum_iff @h).1 hg,
    by simp [tsum, hf, hg, h0])

lemma tsum_eq_tsum_of_has_sum_iff_has_sum {f : β → α} {g : γ → α}
  (h : ∀{a}, has_sum f a ↔ has_sum g a) :
  ∑'b, f b = ∑'c, g c :=
(equiv.refl α).tsum_eq_tsum_of_has_sum_iff_has_sum rfl @h

lemma equiv.tsum_eq (j : γ ≃ β) (f : β → α) : ∑'c, f (j c) = ∑'b, f b :=
tsum_eq_tsum_of_has_sum_iff_has_sum $ λ a, j.has_sum_iff

lemma equiv.tsum_eq_tsum_of_support {f : β → α} {g : γ → α} (e : support f ≃ support g)
  (he : ∀ x, g (e x) = f x) :
  (∑' x, f x) = ∑' y, g y :=
tsum_eq_tsum_of_has_sum_iff_has_sum $ λ _, e.has_sum_iff_of_support he

lemma tsum_eq_tsum_of_ne_zero_bij {g : γ → α} (i : support g → β)
  (hi : ∀ ⦃x y⦄, i x = i y → (x : γ) = y)
  (hf : support f ⊆ set.range i) (hfg : ∀ x, f (i x) = g x) :
  ∑' x, f x  = ∑' y, g y :=
tsum_eq_tsum_of_has_sum_iff_has_sum $ λ _, has_sum_iff_has_sum_of_ne_zero_bij i hi hf hfg

lemma tsum_subtype (s : set β) (f : β → α) :
  ∑' x:s, f x = ∑' x, s.indicator f x :=
tsum_eq_tsum_of_has_sum_iff_has_sum $ λ _, has_sum_subtype_iff_indicator

section has_continuous_add
variable [has_continuous_add α]

lemma tsum_add (hf : summable f) (hg : summable g) : ∑'b, (f b + g b) = (∑'b, f b) + (∑'b, g b) :=
(hf.has_sum.add hg.has_sum).tsum_eq

lemma tsum_sum {f : γ → β → α} {s : finset γ} (hf : ∀i∈s, summable (f i)) :
  ∑'b, ∑ i in s, f i b = ∑ i in s, ∑'b, f i b :=
(has_sum_sum $ assume i hi, (hf i hi).has_sum).tsum_eq

lemma tsum_sigma' [regular_space α] {γ : β → Type*} {f : (Σb:β, γ b) → α}
  (h₁ : ∀b, summable (λc, f ⟨b, c⟩)) (h₂ : summable f) : ∑'p, f p = ∑'b c, f ⟨b, c⟩ :=
(h₂.has_sum.sigma (assume b, (h₁ b).has_sum)).tsum_eq.symm

lemma tsum_prod' [regular_space α] {f : β × γ → α} (h : summable f)
  (h₁ : ∀b, summable (λc, f (b, c))) :
  ∑'p, f p = ∑'b c, f (b, c) :=
(h.has_sum.prod_fiberwise (assume b, (h₁ b).has_sum)).tsum_eq.symm

lemma tsum_comm' [regular_space α] {f : β → γ → α} (h : summable (function.uncurry f))
  (h₁ : ∀b, summable (f b)) (h₂ : ∀ c, summable (λ b, f b c)) :
  ∑' c b, f b c = ∑' b c, f b c :=
begin
  erw [← tsum_prod' h h₁, ← tsum_prod' h.prod_symm h₂, ← (equiv.prod_comm β γ).tsum_eq],
  refl,
  assumption
end

end has_continuous_add

section encodable
open encodable
variable [encodable γ]

/-- You can compute a sum over an encodably type by summing over the natural numbers and
  taking a supremum. This is useful for outer measures. -/
theorem tsum_supr_decode₂ [complete_lattice β] (m : β → α) (m0 : m ⊥ = 0)
  (s : γ → β) : ∑' i : ℕ, m (⨆ b ∈ decode₂ γ i, s b) = ∑' b : γ, m (s b) :=
begin
  have H : ∀ n, m (⨆ b ∈ decode₂ γ n, s b) ≠ 0 → (decode₂ γ n).is_some,
  { intros n h,
    cases decode₂ γ n with b,
    { refine (h $ by simp [m0]).elim },
    { exact rfl } },
  symmetry, refine tsum_eq_tsum_of_ne_zero_bij (λ a, option.get (H a.1 a.2)) _ _ _,
  { rintros ⟨m, hm⟩ ⟨n, hn⟩ e,
    have := mem_decode₂.1 (option.get_mem (H n hn)),
    rwa [← e, mem_decode₂.1 (option.get_mem (H m hm))] at this },
  { intros b h,
    refine ⟨⟨encode b, _⟩, _⟩,
    { simp only [mem_support, encodek₂] at h ⊢, convert h, simp [set.ext_iff, encodek₂] },
    { exact option.get_of_mem _ (encodek₂ _) } },
  { rintros ⟨n, h⟩, dsimp only [subtype.coe_mk],
    transitivity, swap,
    rw [show decode₂ γ n = _, from option.get_mem (H n h)],
    congr, simp [ext_iff, -option.some_get] }
end

/-- `tsum_supr_decode₂` specialized to the complete lattice of sets. -/
theorem tsum_Union_decode₂ (m : set β → α) (m0 : m ∅ = 0)
  (s : γ → set β) : ∑' i, m (⋃ b ∈ decode₂ γ i, s b) = ∑' b, m (s b) :=
tsum_supr_decode₂ m m0 s

/-! Some properties about measure-like functions.
  These could also be functions defined on complete sublattices of sets, with the property
  that they are countably sub-additive.
  `R` will probably be instantiated with `(≤)` in all applications.
-/

/-- If a function is countably sub-additive then it is sub-additive on encodable types -/
theorem rel_supr_tsum [complete_lattice β] (m : β → α) (m0 : m ⊥ = 0)
  (R : α → α → Prop) (m_supr : ∀(s : ℕ → β), R (m (⨆ i, s i)) ∑' i, m (s i))
  (s : γ → β) : R (m (⨆ b : γ, s b)) ∑' b : γ, m (s b) :=
by { rw [← supr_decode₂, ← tsum_supr_decode₂ _ m0 s], exact m_supr _ }

/-- If a function is countably sub-additive then it is sub-additive on finite sets -/
theorem rel_supr_sum [complete_lattice β] (m : β → α) (m0 : m ⊥ = 0)
  (R : α → α → Prop) (m_supr : ∀(s : ℕ → β), R (m (⨆ i, s i)) (∑' i, m (s i)))
  (s : δ → β) (t : finset δ) :
  R (m (⨆ d ∈ t, s d)) (∑ d in t, m (s d)) :=
by { cases t.nonempty_encodable, rw [supr_subtype'], convert rel_supr_tsum m m0 R m_supr _,
     rw [← finset.tsum_subtype], assumption }

/-- If a function is countably sub-additive then it is binary sub-additive -/
theorem rel_sup_add [complete_lattice β] (m : β → α) (m0 : m ⊥ = 0)
  (R : α → α → Prop) (m_supr : ∀(s : ℕ → β), R (m (⨆ i, s i)) (∑' i, m (s i)))
  (s₁ s₂ : β) : R (m (s₁ ⊔ s₂)) (m s₁ + m s₂) :=
begin
  convert rel_supr_tsum m m0 R m_supr (λ b, cond b s₁ s₂),
  { simp only [supr_bool_eq, cond] },
  { rw [tsum_fintype, fintype.sum_bool, cond, cond] }
end

end encodable

variables [has_continuous_add α]

lemma tsum_add_tsum_compl {s : set β} (hs : summable (f ∘ coe : s → α))
  (hsc : summable (f ∘ coe : sᶜ → α)) :
  (∑' x : s, f x) + (∑' x : sᶜ, f x) = ∑' x, f x :=
(hs.has_sum.add_compl hsc.has_sum).tsum_eq.symm

lemma tsum_union_disjoint {s t : set β} (hd : disjoint s t)
  (hs : summable (f ∘ coe : s → α)) (ht : summable (f ∘ coe : t → α)) :
  (∑' x : s ∪ t, f x) = (∑' x : s, f x) + (∑' x : t, f x) :=
(hs.has_sum.add_disjoint hd ht.has_sum).tsum_eq

lemma tsum_even_add_odd {f : ℕ → α} (he : summable (λ k, f (2 * k)))
  (ho : summable (λ k, f (2 * k + 1))) :
  (∑' k, f (2 * k)) + (∑' k, f (2 * k + 1)) = ∑' k, f k :=
(he.has_sum.even_add_odd ho.has_sum).tsum_eq.symm

end tsum

section prod

variables [add_comm_monoid α] [topological_space α] [add_comm_monoid γ] [topological_space γ]

lemma has_sum.prod_mk {f : β → α} {g : β → γ} {a : α} {b : γ}
  (hf : has_sum f a) (hg : has_sum g b) :
  has_sum (λ x, (⟨f x, g x⟩ : α × γ)) ⟨a, b⟩ :=
by simp [has_sum, ← prod_mk_sum, filter.tendsto.prod_mk_nhds hf hg]

end prod

section pi
variables {ι : Type*} {π : α → Type*} [∀ x, add_comm_monoid (π x)] [∀ x, topological_space (π x)]

lemma pi.has_sum {f : ι → ∀ x, π x} {g : ∀ x, π x} :
  has_sum f g ↔ ∀ x, has_sum (λ i, f i x) (g x) :=
by simp only [has_sum, tendsto_pi_nhds, sum_apply]

lemma pi.summable {f : ι → ∀ x, π x} : summable f ↔ ∀ x, summable (λ i, f i x) :=
by simp only [summable, pi.has_sum, skolem]

lemma tsum_apply [∀ x, t2_space (π x)] {f : ι → ∀ x, π x}{x : α} (hf : summable f) :
  (∑' i, f i) x = ∑' i, f i x :=
(pi.has_sum.mp hf.has_sum x).tsum_eq.symm

end pi

section topological_group
variables [add_comm_group α] [topological_space α] [topological_add_group α]
variables {f g : β → α} {a a₁ a₂ : α}

-- `by simpa using` speeds up elaboration. Why?
lemma has_sum.neg (h : has_sum f a) : has_sum (λb, - f b) (- a) :=
by simpa only using h.map (-add_monoid_hom.id α) continuous_neg

lemma summable.neg (hf : summable f) : summable (λb, - f b) :=
hf.has_sum.neg.summable

lemma summable.of_neg (hf : summable (λb, - f b)) : summable f :=
by simpa only [neg_neg] using hf.neg

lemma summable_neg_iff : summable (λ b, - f b) ↔ summable f :=
⟨summable.of_neg, summable.neg⟩

lemma has_sum.sub (hf : has_sum f a₁) (hg : has_sum g a₂) : has_sum (λb, f b - g b) (a₁ - a₂) :=
by { simp only [sub_eq_add_neg], exact hf.add hg.neg }

lemma summable.sub (hf : summable f) (hg : summable g) : summable (λb, f b - g b) :=
(hf.has_sum.sub hg.has_sum).summable

lemma summable.trans_sub (hg : summable g) (hfg : summable (λb, f b - g b)) :
  summable f :=
by simpa only [sub_add_cancel] using hfg.add hg

lemma summable_iff_of_summable_sub (hfg : summable (λb, f b - g b)) :
  summable f ↔ summable g :=
⟨λ hf, hf.trans_sub $ by simpa only [neg_sub] using hfg.neg, λ hg, hg.trans_sub hfg⟩

lemma has_sum.update (hf : has_sum f a₁) (b : β) [decidable_eq β] (a : α) :
  has_sum (update f b a) (a - f b + a₁) :=
begin
  convert ((has_sum_ite_eq b _).add hf),
  ext b',
  by_cases h : b' = b,
  { rw [h, update_same],
    simp only [eq_self_iff_true, if_true, sub_add_cancel] },
  simp only [h, update_noteq, if_false, ne.def, zero_add, not_false_iff],
end

lemma summable.update (hf : summable f) (b : β) [decidable_eq β] (a : α) :
  summable (update f b a) :=
(hf.has_sum.update b a).summable

lemma has_sum.has_sum_compl_iff {s : set β} (hf : has_sum (f ∘ coe : s → α) a₁) :
  has_sum (f ∘ coe : sᶜ → α) a₂ ↔ has_sum f (a₁ + a₂) :=
begin
  refine ⟨λ h, hf.add_compl h, λ h, _⟩,
  rw [has_sum_subtype_iff_indicator] at hf ⊢,
  rw [set.indicator_compl],
  simpa only [add_sub_cancel'] using h.sub hf
end

lemma has_sum.has_sum_iff_compl {s : set β} (hf : has_sum (f ∘ coe : s → α) a₁) :
  has_sum f a₂ ↔ has_sum (f ∘ coe : sᶜ → α) (a₂ - a₁) :=
iff.symm $ hf.has_sum_compl_iff.trans $ by rw [add_sub_cancel'_right]

lemma summable.summable_compl_iff {s : set β} (hf : summable (f ∘ coe : s → α)) :
  summable (f ∘ coe : sᶜ → α) ↔ summable f :=
⟨λ ⟨a, ha⟩, (hf.has_sum.has_sum_compl_iff.1 ha).summable,
  λ ⟨a, ha⟩, (hf.has_sum.has_sum_iff_compl.1 ha).summable⟩

protected lemma finset.has_sum_compl_iff (s : finset β) :
  has_sum (λ x : {x // x ∉ s}, f x) a ↔ has_sum f (a + ∑ i in s, f i) :=
(s.has_sum f).has_sum_compl_iff.trans $ by rw [add_comm]

protected lemma finset.has_sum_iff_compl (s : finset β) :
  has_sum f a ↔ has_sum (λ x : {x // x ∉ s}, f x) (a - ∑ i in s, f i) :=
(s.has_sum f).has_sum_iff_compl

protected lemma finset.summable_compl_iff (s : finset β) :
  summable (λ x : {x // x ∉ s}, f x) ↔ summable f :=
(s.summable f).summable_compl_iff

lemma set.finite.summable_compl_iff {s : set β} (hs : s.finite) :
  summable (f ∘ coe : sᶜ → α) ↔ summable f :=
(hs.summable f).summable_compl_iff

lemma has_sum_ite_eq_extract [decidable_eq β] (hf : has_sum f a) (b : β) :
  has_sum (λ n, ite (n = b) 0 (f n)) (a - f b) :=
begin
  convert hf.update b 0 using 1,
  { ext n, rw function.update_apply, },
  { rw [sub_add_eq_add_sub, zero_add], },
end

section tsum
variables [t2_space α]

lemma tsum_neg (hf : summable f) : ∑'b, - f b = - ∑'b, f b :=
hf.has_sum.neg.tsum_eq

lemma tsum_sub (hf : summable f) (hg : summable g) : ∑'b, (f b - g b) = ∑'b, f b - ∑'b, g b :=
(hf.has_sum.sub hg.has_sum).tsum_eq

lemma sum_add_tsum_compl {s : finset β} (hf : summable f) :
  (∑ x in s, f x) + (∑' x : (↑s : set β)ᶜ, f x) = ∑' x, f x :=
((s.has_sum f).add_compl (s.summable_compl_iff.2 hf).has_sum).tsum_eq.symm

/-- Let `f : β → α` be a sequence with summable series and let `b ∈ β` be an index.
Lemma `tsum_ite_eq_extract` writes `Σ f n` as the sum of `f b` plus the series of the
remaining terms. -/
lemma tsum_ite_eq_extract [decidable_eq β] (hf : summable f) (b : β) :
  ∑' n, f n = f b + ∑' n, ite (n = b) 0 (f n) :=
begin
  rw (has_sum_ite_eq_extract hf.has_sum b).tsum_eq,
  exact (add_sub_cancel'_right _ _).symm,
end

end tsum

/-!
### Sums on subtypes

If `s` is a finset of `α`, we show that the summability of `f` in the whole space and on the subtype
`univ - s` are equivalent, and relate their sums. For a function defined on `ℕ`, we deduce the
formula `(∑ i in range k, f i) + (∑' i, f (i + k)) = (∑' i, f i)`, in `sum_add_tsum_nat_add`.
-/
section subtype

lemma has_sum_nat_add_iff {f : ℕ → α} (k : ℕ) {a : α} :
  has_sum (λ n, f (n + k)) a ↔ has_sum f (a + ∑ i in range k, f i) :=
begin
  refine iff.trans _ ((range k).has_sum_compl_iff),
  rw [← (not_mem_range_equiv k).symm.has_sum_iff],
  refl
end

lemma summable_nat_add_iff {f : ℕ → α} (k : ℕ) : summable (λ n, f (n + k)) ↔ summable f :=
iff.symm $ (equiv.add_right (∑ i in range k, f i)).summable_iff_of_has_sum_iff $
  λ a, (has_sum_nat_add_iff k).symm

lemma has_sum_nat_add_iff' {f : ℕ → α} (k : ℕ) {a : α} :
  has_sum (λ n, f (n + k)) (a - ∑ i in range k, f i) ↔ has_sum f a :=
by simp [has_sum_nat_add_iff]

lemma sum_add_tsum_nat_add [t2_space α] {f : ℕ → α} (k : ℕ) (h : summable f) :
  (∑ i in range k, f i) + (∑' i, f (i + k)) = ∑' i, f i :=
by simpa only [add_comm] using
  ((has_sum_nat_add_iff k).1 ((summable_nat_add_iff k).2 h).has_sum).unique h.has_sum

lemma tsum_eq_zero_add [t2_space α] {f : ℕ → α} (hf : summable f) :
  ∑'b, f b = f 0 + ∑'b, f (b + 1) :=
by simpa only [sum_range_one] using (sum_add_tsum_nat_add 1 hf).symm

/-- For `f : ℕ → α`, then `∑' k, f (k + i)` tends to zero. This does not require a summability
assumption on `f`, as otherwise all sums are zero. -/
lemma tendsto_sum_nat_add [t2_space α] (f : ℕ → α) : tendsto (λ i, ∑' k, f (k + i)) at_top (𝓝 0) :=
begin
  by_cases hf : summable f,
  { have h₀ : (λ i, (∑' i, f i) - ∑ j in range i, f j) = λ i, ∑' (k : ℕ), f (k + i),
    { ext1 i,
      rw [sub_eq_iff_eq_add, add_comm, sum_add_tsum_nat_add i hf] },
    have h₁ : tendsto (λ i : ℕ, ∑' i, f i) at_top (𝓝 (∑' i, f i)) := tendsto_const_nhds,
    simpa only [h₀, sub_self] using tendsto.sub h₁ hf.has_sum.tendsto_sum_nat },
  { convert tendsto_const_nhds,
    ext1 i,
    rw ← summable_nat_add_iff i at hf,
    { exact tsum_eq_zero_of_not_summable hf },
    { apply_instance } }
end

end subtype

end topological_group

section topological_ring
variables [semiring α] [topological_space α] [topological_ring α]
variables {f g : β → α} {a a₁ a₂ : α}
lemma has_sum.mul_left (a₂) (h : has_sum f a₁) : has_sum (λb, a₂ * f b) (a₂ * a₁) :=
by simpa only using h.map (add_monoid_hom.mul_left a₂) (continuous_const.mul continuous_id)

lemma has_sum.mul_right (a₂) (hf : has_sum f a₁) : has_sum (λb, f b * a₂) (a₁ * a₂) :=
by simpa only using hf.map (add_monoid_hom.mul_right a₂) (continuous_id.mul continuous_const)

lemma summable.mul_left (a) (hf : summable f) : summable (λb, a * f b) :=
(hf.has_sum.mul_left _).summable

lemma summable.mul_right (a) (hf : summable f) : summable (λb, f b * a) :=
(hf.has_sum.mul_right _).summable

section tsum
variables [t2_space α]

lemma summable.tsum_mul_left (a) (hf : summable f) : ∑'b, a * f b = a * ∑'b, f b :=
(hf.has_sum.mul_left _).tsum_eq

lemma summable.tsum_mul_right (a) (hf : summable f) : (∑'b, f b * a) = (∑'b, f b) * a :=
(hf.has_sum.mul_right _).tsum_eq

end tsum

end topological_ring

section const_smul
variables {R : Type*}
[monoid R] [topological_space R]
[topological_space α] [add_comm_monoid α]
[distrib_mul_action R α] [has_continuous_smul R α]
{f : β → α}

lemma has_sum.const_smul {a : α} {r : R} (hf : has_sum f a) : has_sum (λ z, r • f z) (r • a) :=
hf.map (distrib_mul_action.to_add_monoid_hom α r) (continuous_const.smul continuous_id)

lemma summable.const_smul {r : R} (hf : summable f) : summable (λ z, r • f z) :=
hf.has_sum.const_smul.summable

lemma tsum_const_smul [t2_space α] {r : R} (hf : summable f) : ∑' z, r • f z = r • ∑' z, f z :=
hf.has_sum.const_smul.tsum_eq

end const_smul

section smul_const
variables {R : Type*}
[semiring R] [topological_space R]
[topological_space α] [add_comm_monoid α]
[module R α] [has_continuous_smul R α]
{f : β → R}

lemma has_sum.smul_const {a : α} {r : R} (hf : has_sum f r) : has_sum (λ z, f z • a) (r • a) :=
hf.map ((smul_add_hom R α).flip a) (continuous_id.smul continuous_const)

lemma summable.smul_const {a : α} (hf : summable f) : summable (λ z, f z • a) :=
hf.has_sum.smul_const.summable

lemma tsum_smul_const [t2_space α] {a : α} (hf : summable f) : ∑' z, f z • a = (∑' z, f z) • a :=
hf.has_sum.smul_const.tsum_eq

end smul_const

section division_ring

variables [division_ring α] [topological_space α] [topological_ring α]
{f g : β → α} {a a₁ a₂ : α}

lemma has_sum.div_const (h : has_sum f a) (b : α) : has_sum (λ x, f x / b) (a / b) :=
by simp only [div_eq_mul_inv, h.mul_right b⁻¹]

lemma summable.div_const (h : summable f) (b : α) : summable (λ x, f x / b) :=
(h.has_sum.div_const b).summable

lemma has_sum_mul_left_iff (h : a₂ ≠ 0) : has_sum f a₁ ↔ has_sum (λb, a₂ * f b) (a₂ * a₁) :=
⟨has_sum.mul_left _, λ H, by simpa only [inv_mul_cancel_left₀ h] using H.mul_left a₂⁻¹⟩

lemma has_sum_mul_right_iff (h : a₂ ≠ 0) : has_sum f a₁ ↔ has_sum (λb, f b * a₂) (a₁ * a₂) :=
⟨has_sum.mul_right _, λ H, by simpa only [mul_inv_cancel_right₀ h] using H.mul_right a₂⁻¹⟩

lemma summable_mul_left_iff (h : a ≠ 0) : summable f ↔ summable (λb, a * f b) :=
⟨λ H, H.mul_left _, λ H, by simpa only [inv_mul_cancel_left₀ h] using H.mul_left a⁻¹⟩

lemma summable_mul_right_iff (h : a ≠ 0) : summable f ↔ summable (λb, f b * a) :=
⟨λ H, H.mul_right _, λ H, by simpa only [mul_inv_cancel_right₀ h] using H.mul_right a⁻¹⟩

lemma tsum_mul_left [t2_space α] : (∑' x, a * f x) = a * ∑' x, f x :=
if hf : summable f then hf.tsum_mul_left a
else if ha : a = 0 then by simp [ha]
else by rw [tsum_eq_zero_of_not_summable hf,
  tsum_eq_zero_of_not_summable (mt (summable_mul_left_iff ha).2 hf), mul_zero]

lemma tsum_mul_right [t2_space α] : (∑' x, f x * a) = (∑' x, f x) * a :=
if hf : summable f then hf.tsum_mul_right a
else if ha : a = 0 then by simp [ha]
else by rw [tsum_eq_zero_of_not_summable hf,
  tsum_eq_zero_of_not_summable (mt (summable_mul_right_iff ha).2 hf), zero_mul]

end division_ring

section order_topology
variables [ordered_add_comm_monoid α] [topological_space α] [order_closed_topology α]
variables {f g : β → α} {a a₁ a₂ : α}

lemma has_sum_le (h : ∀b, f b ≤ g b) (hf : has_sum f a₁) (hg : has_sum g a₂) : a₁ ≤ a₂ :=
le_of_tendsto_of_tendsto' hf hg $ assume s, sum_le_sum $ assume b _, h b

@[mono] lemma has_sum_mono (hf : has_sum f a₁) (hg : has_sum g a₂) (h : f ≤ g) : a₁ ≤ a₂ :=
has_sum_le h hf hg

lemma has_sum_le_of_sum_le (hf : has_sum f a) (h : ∀ s : finset β, ∑ b in s, f b ≤ a₂) :
  a ≤ a₂ :=
le_of_tendsto' hf h

lemma le_has_sum_of_le_sum (hf : has_sum f a) (h : ∀ s : finset β, a₂ ≤ ∑ b in s, f b) :
  a₂ ≤ a :=
ge_of_tendsto' hf h

lemma has_sum_le_inj {g : γ → α} (i : β → γ) (hi : injective i) (hs : ∀c∉set.range i, 0 ≤ g c)
  (h : ∀b, f b ≤ g (i b)) (hf : has_sum f a₁) (hg : has_sum g a₂) : a₁ ≤ a₂ :=
have has_sum (λc, (partial_inv i c).cases_on' 0 f) a₁,
begin
  refine (has_sum_iff_has_sum_of_ne_zero_bij (i ∘ coe) _ _ _).2 hf,
  { exact assume c₁ c₂ eq, hi eq },
  { intros c hc,
    rw [mem_support] at hc,
    cases eq : partial_inv i c with b; rw eq at hc,
    { contradiction },
    { rw [partial_inv_of_injective hi] at eq,
      exact ⟨⟨b, hc⟩, eq⟩ } },
  { assume c, simp [partial_inv_left hi, option.cases_on'] }
end,
begin
  refine has_sum_le (assume c, _) this hg,
  by_cases c ∈ set.range i,
  { rcases h with ⟨b, rfl⟩,
    rw [partial_inv_left hi, option.cases_on'],
    exact h _ },
  { have : partial_inv i c = none := dif_neg h,
    rw [this, option.cases_on'],
    exact hs _ h }
end

lemma tsum_le_tsum_of_inj {g : γ → α} (i : β → γ) (hi : injective i) (hs : ∀c∉set.range i, 0 ≤ g c)
  (h : ∀b, f b ≤ g (i b)) (hf : summable f) (hg : summable g) : tsum f ≤ tsum g :=
has_sum_le_inj i hi hs h hf.has_sum hg.has_sum

lemma sum_le_has_sum (s : finset β) (hs : ∀ b∉s, 0 ≤ f b) (hf : has_sum f a) :
  ∑ b in s, f b ≤ a :=
ge_of_tendsto hf (eventually_at_top.2 ⟨s, λ t hst,
  sum_le_sum_of_subset_of_nonneg hst $ λ b hbt hbs, hs b hbs⟩)

lemma is_lub_has_sum (h : ∀ b, 0 ≤ f b) (hf : has_sum f a) :
  is_lub (set.range (λ s : finset β, ∑ b in s, f b)) a :=
is_lub_of_tendsto (finset.sum_mono_set_of_nonneg h) hf

lemma le_has_sum (hf : has_sum f a) (b : β) (hb : ∀ b' ≠ b, 0 ≤ f b') : f b ≤ a :=
calc f b = ∑ b in {b}, f b : finset.sum_singleton.symm
... ≤ a : sum_le_has_sum _ (by { convert hb, simp }) hf

lemma sum_le_tsum {f : β → α} (s : finset β) (hs : ∀ b∉s, 0 ≤ f b) (hf : summable f) :
  ∑ b in s, f b ≤ ∑' b, f b :=
sum_le_has_sum s hs hf.has_sum

lemma le_tsum (hf : summable f) (b : β) (hb : ∀ b' ≠ b, 0 ≤ f b') : f b ≤ ∑' b, f b :=
le_has_sum (summable.has_sum hf) b hb

lemma tsum_le_tsum (h : ∀b, f b ≤ g b) (hf : summable f) (hg : summable g) : ∑'b, f b ≤ ∑'b, g b :=
has_sum_le h hf.has_sum hg.has_sum

@[mono] lemma tsum_mono (hf : summable f) (hg : summable g) (h : f ≤ g) :
  ∑' n, f n ≤ ∑' n, g n :=
tsum_le_tsum h hf hg

lemma tsum_le_of_sum_le (hf : summable f) (h : ∀ s : finset β, ∑ b in s, f b ≤ a₂) :
  ∑' b, f b ≤ a₂ :=
has_sum_le_of_sum_le hf.has_sum h

lemma tsum_le_of_sum_le' (ha₂ : 0 ≤ a₂) (h : ∀ s : finset β, ∑ b in s, f b ≤ a₂) :
  ∑' b, f b ≤ a₂ :=
begin
  by_cases hf : summable f,
  { exact tsum_le_of_sum_le hf h },
  { rw tsum_eq_zero_of_not_summable hf,
    exact ha₂ }
end

lemma has_sum.nonneg (h : ∀ b, 0 ≤ g b) (ha : has_sum g a) : 0 ≤ a :=
has_sum_le h has_sum_zero ha

lemma has_sum.nonpos (h : ∀ b, g b ≤ 0) (ha : has_sum g a) : a ≤ 0 :=
has_sum_le h ha has_sum_zero

lemma tsum_nonneg (h : ∀ b, 0 ≤ g b) : 0 ≤ ∑'b, g b :=
begin
  by_cases hg : summable g,
  { exact hg.has_sum.nonneg h },
  { simp [tsum_eq_zero_of_not_summable hg] }
end

lemma tsum_nonpos (h : ∀ b, f b ≤ 0) : ∑'b, f b ≤ 0 :=
begin
  by_cases hf : summable f,
  { exact hf.has_sum.nonpos h },
  { simp [tsum_eq_zero_of_not_summable hf] }
end

end order_topology

section ordered_topological_group

variables [ordered_add_comm_group α] [topological_space α] [topological_add_group α]
  [order_closed_topology α] {f g : β → α} {a₁ a₂ : α}

lemma has_sum_lt {i : β} (h : ∀ (b : β), f b ≤ g b) (hi : f i < g i)
  (hf : has_sum f a₁) (hg : has_sum g a₂) :
  a₁ < a₂ :=
have update f i 0 ≤ update g i 0 := update_le_update_iff.mpr ⟨rfl.le, λ i _, h i⟩,
have 0 - f i + a₁ ≤ 0 - g i + a₂ := has_sum_le this (hf.update i 0) (hg.update i 0),
by simpa only [zero_sub, add_neg_cancel_left] using add_lt_add_of_lt_of_le hi this

@[mono] lemma has_sum_strict_mono (hf : has_sum f a₁) (hg : has_sum g a₂) (h : f < g) : a₁ < a₂ :=
let ⟨hle, i, hi⟩ := pi.lt_def.mp h in has_sum_lt hle hi hf hg

lemma tsum_lt_tsum {i : β} (h : ∀ (b : β), f b ≤ g b) (hi : f i < g i)
  (hf : summable f) (hg : summable g) :
  ∑' n, f n < ∑' n, g n :=
has_sum_lt h hi hf.has_sum hg.has_sum

@[mono] lemma tsum_strict_mono (hf : summable f) (hg : summable g) (h : f < g) :
  ∑' n, f n < ∑' n, g n :=
let ⟨hle, i, hi⟩ := pi.lt_def.mp h in tsum_lt_tsum hle hi hf hg

lemma tsum_pos (hsum : summable g) (hg : ∀ b, 0 ≤ g b) (i : β) (hi : 0 < g i) :
  0 < ∑' b, g b :=
by { rw ← tsum_zero, exact tsum_lt_tsum hg hi summable_zero hsum }

end ordered_topological_group

section canonically_ordered
variables [canonically_ordered_add_monoid α] [topological_space α] [order_closed_topology α]
variables {f : β → α} {a : α}

lemma le_has_sum' (hf : has_sum f a) (b : β) : f b ≤ a :=
le_has_sum hf b $ λ _ _, zero_le _

lemma le_tsum' (hf : summable f) (b : β) : f b ≤ ∑' b, f b :=
le_tsum hf b $ λ _ _, zero_le _

lemma has_sum_zero_iff : has_sum f 0 ↔ ∀ x, f x = 0 :=
begin
  refine ⟨_, λ h, _⟩,
  { contrapose!,
    exact λ ⟨x, hx⟩ h, irrefl _ (lt_of_lt_of_le (pos_iff_ne_zero.2 hx) (le_has_sum' h x)) },
  { convert has_sum_zero,
    exact funext h }
end

lemma tsum_eq_zero_iff (hf : summable f) : ∑' i, f i = 0 ↔ ∀ x, f x = 0 :=
by rw [←has_sum_zero_iff, hf.has_sum_iff]

lemma tsum_ne_zero_iff (hf : summable f) : ∑' i, f i ≠ 0 ↔ ∃ x, f x ≠ 0 :=
by rw [ne.def, tsum_eq_zero_iff hf, not_forall]

lemma is_lub_has_sum' (hf : has_sum f a) : is_lub (set.range (λ s : finset β, ∑ b in s, f b)) a :=
is_lub_of_tendsto (finset.sum_mono_set f) hf

end canonically_ordered

section uniform_group

variables [add_comm_group α] [uniform_space α]

lemma summable_iff_cauchy_seq_finset [complete_space α] {f : β → α} :
  summable f ↔ cauchy_seq (λ (s : finset β), ∑ b in s, f b) :=
cauchy_map_iff_exists_tendsto.symm

variables [uniform_add_group α] {f g : β → α} {a a₁ a₂ : α}

lemma cauchy_seq_finset_iff_vanishing :
  cauchy_seq (λ (s : finset β), ∑ b in s, f b)
  ↔ ∀ e ∈ 𝓝 (0:α), (∃s:finset β, ∀t, disjoint t s → ∑ b in t, f b ∈ e) :=
begin
  simp only [cauchy_seq, cauchy_map_iff, and_iff_right at_top_ne_bot,
    prod_at_top_at_top_eq, uniformity_eq_comap_nhds_zero α, tendsto_comap_iff, (∘)],
  rw [tendsto_at_top'],
  split,
  { assume h e he,
    rcases h e he with ⟨⟨s₁, s₂⟩, h⟩,
    use [s₁ ∪ s₂],
    assume t ht,
    specialize h (s₁ ∪ s₂, (s₁ ∪ s₂) ∪ t) ⟨le_sup_left, le_sup_of_le_left le_sup_right⟩,
    simpa only [finset.sum_union ht.symm, add_sub_cancel'] using h },
  { assume h e he,
    rcases exists_nhds_half_neg he with ⟨d, hd, hde⟩,
    rcases h d hd with ⟨s, h⟩,
    use [(s, s)],
    rintros ⟨t₁, t₂⟩ ⟨ht₁, ht₂⟩,
    have : ∑ b in t₂, f b - ∑ b in t₁, f b = ∑ b in t₂ \ s, f b - ∑ b in t₁ \ s, f b,
    { simp only [(finset.sum_sdiff ht₁).symm, (finset.sum_sdiff ht₂).symm,
        add_sub_add_right_eq_sub] },
    simp only [this],
    exact hde _ (h _ finset.sdiff_disjoint) _ (h _ finset.sdiff_disjoint) }
end

local attribute [instance] topological_add_group.regular_space

/-- The sum over the complement of a finset tends to `0` when the finset grows to cover the whole
space. This does not need a summability assumption, as otherwise all sums are zero. -/
lemma tendsto_tsum_compl_at_top_zero [t1_space α] (f : β → α) :
  tendsto (λ (s : finset β), ∑' b : {x // x ∉ s}, f b) at_top (𝓝 0) :=
begin
  by_cases H : summable f,
  { assume e he,
    rcases nhds_is_closed he with ⟨o, ho, oe, o_closed⟩,
    simp only [le_eq_subset, set.mem_preimage, mem_at_top_sets, filter.mem_map, ge_iff_le],
    obtain ⟨s, hs⟩ : ∃ (s : finset β), ∀ (t : finset β), disjoint t s → ∑ (b : β) in t, f b ∈ o :=
      cauchy_seq_finset_iff_vanishing.1 (tendsto.cauchy_seq H.has_sum) o ho,
    refine ⟨s, λ a sa, oe _⟩,
    have A : summable (λ b : {x // x ∉ a}, f b) := a.summable_compl_iff.2 H,
    apply is_closed.mem_of_tendsto o_closed A.has_sum (eventually_of_forall (λ b, _)),
    have : disjoint (finset.image (λ (i : {x // x ∉ a}), (i : β)) b) s,
    { apply disjoint_left.2 (λ i hi his, _),
      rcases mem_image.1 hi with ⟨i', hi', rfl⟩,
      exact i'.2 (sa his), },
    convert hs _ this using 1,
    rw sum_image,
    assume i hi j hj hij,
    exact subtype.ext hij },
  { convert tendsto_const_nhds,
    ext s,
    apply tsum_eq_zero_of_not_summable,
    rwa finset.summable_compl_iff }
end

variable [complete_space α]

lemma summable_iff_vanishing :
  summable f ↔ ∀ e ∈ 𝓝 (0:α), (∃s:finset β, ∀t, disjoint t s → ∑ b in t, f b ∈ e) :=
by rw [summable_iff_cauchy_seq_finset, cauchy_seq_finset_iff_vanishing]

/- TODO: generalize to monoid with a uniform continuous subtraction operator: `(a + b) - b = a` -/
lemma summable.summable_of_eq_zero_or_self (hf : summable f) (h : ∀b, g b = 0 ∨ g b = f b) :
  summable g :=
summable_iff_vanishing.2 $
  assume e he,
  let ⟨s, hs⟩ := summable_iff_vanishing.1 hf e he in
  ⟨s, assume t ht,
    have eq : ∑ b in t.filter (λb, g b = f b), f b = ∑ b in t, g b :=
      calc ∑ b in t.filter (λb, g b = f b), f b = ∑ b in t.filter (λb, g b = f b), g b :
          finset.sum_congr rfl (assume b hb, (finset.mem_filter.1 hb).2.symm)
        ... = ∑ b in t, g b :
        begin
          refine finset.sum_subset (finset.filter_subset _ _) _,
          assume b hbt hb,
          simp only [(∉), finset.mem_filter, and_iff_right hbt] at hb,
          exact (h b).resolve_right hb
        end,
    eq ▸ hs _ $ finset.disjoint_of_subset_left (finset.filter_subset _ _) ht⟩

protected lemma summable.indicator (hf : summable f) (s : set β) :
  summable (s.indicator f) :=
hf.summable_of_eq_zero_or_self $ set.indicator_eq_zero_or_self _ _

lemma summable.comp_injective {i : γ → β} (hf : summable f) (hi : injective i) :
  summable (f ∘ i) :=
begin
  simpa only [set.indicator_range_comp]
    using (hi.summable_iff _).2 (hf.indicator (set.range i)),
  exact λ x hx, set.indicator_of_not_mem hx _
end

lemma summable.subtype (hf : summable f) (s : set β) : summable (f ∘ coe : s → α) :=
hf.comp_injective subtype.coe_injective

lemma summable_subtype_and_compl {s : set β} :
  summable (λ x : s, f x) ∧ summable (λ x : sᶜ, f x) ↔ summable f :=
⟨and_imp.2 summable.add_compl, λ h, ⟨h.subtype s, h.subtype sᶜ⟩⟩

lemma summable.sigma_factor {γ : β → Type*} {f : (Σb:β, γ b) → α}
  (ha : summable f) (b : β) : summable (λc, f ⟨b, c⟩) :=
ha.comp_injective sigma_mk_injective

lemma summable.sigma [t1_space α] {γ : β → Type*} {f : (Σb:β, γ b) → α}
  (ha : summable f) : summable (λb, ∑'c, f ⟨b, c⟩) :=
ha.sigma' (λ b, ha.sigma_factor b)

lemma summable.prod_factor {f : β × γ → α} (h : summable f) (b : β) :
  summable (λ c, f (b, c)) :=
h.comp_injective $ λ c₁ c₂ h, (prod.ext_iff.1 h).2

lemma tsum_sigma [t1_space α] {γ : β → Type*} {f : (Σb:β, γ b) → α}
  (ha : summable f) : ∑'p, f p = ∑'b c, f ⟨b, c⟩ :=
tsum_sigma' (λ b, ha.sigma_factor b) ha

lemma tsum_prod [t1_space α] {f : β × γ → α} (h : summable f) :
  ∑'p, f p = ∑'b c, f ⟨b, c⟩ :=
tsum_prod' h h.prod_factor

lemma tsum_comm [t1_space α] {f : β → γ → α} (h : summable (function.uncurry f)) :
  ∑' c b, f b c = ∑' b c, f b c :=
tsum_comm' h h.prod_factor h.prod_symm.prod_factor

end uniform_group

section topological_group

variables {G : Type*} [topological_space G] [add_comm_group G] [topological_add_group G]
  {f : α → G}

lemma summable.vanishing (hf : summable f) ⦃e : set G⦄ (he : e ∈ 𝓝 (0 : G)) :
  ∃ s : finset α, ∀ t, disjoint t s → ∑ k in t, f k ∈ e :=
begin
  letI : uniform_space G := topological_add_group.to_uniform_space G,
  letI : uniform_add_group G := topological_add_group_is_uniform,
  rcases hf with ⟨y, hy⟩,
  exact cauchy_seq_finset_iff_vanishing.1 hy.cauchy_seq e he
end

/-- Series divergence test: if `f` is a convergent series, then `f x` tends to zero along
`cofinite`. -/
lemma summable.tendsto_cofinite_zero (hf : summable f) : tendsto f cofinite (𝓝 0) :=
begin
  intros e he,
  rw [filter.mem_map],
  rcases hf.vanishing he with ⟨s, hs⟩,
  refine s.eventually_cofinite_nmem.mono (λ x hx, _),
  by simpa using hs {x} (disjoint_singleton_left.2 hx)
end

lemma summable.tendsto_at_top_zero {f : ℕ → G} (hf : summable f) : tendsto f at_top (𝓝 0) :=
by { rw ←nat.cofinite_eq_at_top, exact hf.tendsto_cofinite_zero }

lemma summable.tendsto_top_of_pos {α : Type*}
  [linear_ordered_field α] [topological_space α] [order_topology α] {f : ℕ → α}
  (hf : summable f⁻¹) (hf' : ∀ n, 0 < f n) : tendsto f at_top at_top :=
begin
  rw [show f = f⁻¹⁻¹, by { ext, simp }],
  apply filter.tendsto.inv_tendsto_zero,
  apply tendsto_nhds_within_of_tendsto_nhds_of_eventually_within _
    (summable.tendsto_at_top_zero hf),
  rw eventually_iff_exists_mem,
  refine ⟨set.Ioi 0, Ioi_mem_at_top _, λ _ _, _⟩,
  rw [set.mem_Ioi, inv_eq_one_div, one_div, pi.inv_apply, _root_.inv_pos],
  exact hf' _,
end

end topological_group

section linear_order

/-! For infinite sums taking values in a linearly ordered monoid, the existence of a least upper
bound for the finite sums is a criterion for summability.

This criterion is useful when applied in a linearly ordered monoid which is also a complete or
conditionally complete linear order, such as `ℝ`, `ℝ≥0`, `ℝ≥0∞`, because it is then easy to check
the existence of a least upper bound.
-/

lemma has_sum_of_is_lub_of_nonneg [linear_ordered_add_comm_monoid β] [topological_space β]
  [order_topology β] {f : α → β} (b : β) (h : ∀ b, 0 ≤ f b)
  (hf : is_lub (set.range (λ s, ∑ a in s, f a)) b) :
  has_sum f b :=
tendsto_at_top_is_lub (finset.sum_mono_set_of_nonneg h) hf

lemma has_sum_of_is_lub [canonically_linear_ordered_add_monoid β] [topological_space β]
   [order_topology β] {f : α → β} (b : β) (hf : is_lub (set.range (λ s, ∑ a in s, f a)) b) :
  has_sum f b :=
tendsto_at_top_is_lub (finset.sum_mono_set f) hf

lemma summable_abs_iff [linear_ordered_add_comm_group β] [uniform_space β]
  [uniform_add_group β] [complete_space β] {f : α → β} :
  summable (λ x, |f x|) ↔ summable f :=
have h1 : ∀ x : {x | 0 ≤ f x}, |f x| = f x := λ x, abs_of_nonneg x.2,
have h2 : ∀ x : {x | 0 ≤ f x}ᶜ, |f x| = -f x := λ x, abs_of_neg (not_le.1 x.2),
calc summable (λ x, |f x|) ↔
  summable (λ x : {x | 0 ≤ f x}, |f x|) ∧ summable (λ x : {x | 0 ≤ f x}ᶜ, |f x|) :
  summable_subtype_and_compl.symm
... ↔ summable (λ x : {x | 0 ≤ f x}, f x) ∧ summable (λ x : {x | 0 ≤ f x}ᶜ, -f x) :
  by simp only [h1, h2]
... ↔ _ : by simp only [summable_neg_iff, summable_subtype_and_compl]

alias summable_abs_iff ↔ summable.of_abs summable.abs

end linear_order

section cauchy_seq
open filter

/-- If the extended distance between consecutive points of a sequence is estimated
by a summable series of `nnreal`s, then the original sequence is a Cauchy sequence. -/
lemma cauchy_seq_of_edist_le_of_summable [pseudo_emetric_space α] {f : ℕ → α} (d : ℕ → ℝ≥0)
  (hf : ∀ n, edist (f n) (f n.succ) ≤ d n) (hd : summable d) : cauchy_seq f :=
begin
  refine emetric.cauchy_seq_iff_nnreal.2 (λ ε εpos, _),
  -- Actually we need partial sums of `d` to be a Cauchy sequence
  replace hd : cauchy_seq (λ (n : ℕ), ∑ x in range n, d x) :=
    let ⟨_, H⟩ := hd in H.tendsto_sum_nat.cauchy_seq,
  -- Now we take the same `N` as in one of the definitions of a Cauchy sequence
  refine (metric.cauchy_seq_iff'.1 hd ε (nnreal.coe_pos.2 εpos)).imp (λ N hN n hn, _),
  have hsum := hN n hn,
  -- We simplify the known inequality
  rw [dist_nndist, nnreal.nndist_eq, ← sum_range_add_sum_Ico _ hn, add_tsub_cancel_left] at hsum,
  norm_cast at hsum,
  replace hsum := lt_of_le_of_lt (le_max_left _ _) hsum,
  rw edist_comm,
  -- Then use `hf` to simplify the goal to the same form
  apply lt_of_le_of_lt (edist_le_Ico_sum_of_edist_le hn (λ k _ _, hf k)),
  assumption_mod_cast
end

/-- If the distance between consecutive points of a sequence is estimated by a summable series,
then the original sequence is a Cauchy sequence. -/
lemma cauchy_seq_of_dist_le_of_summable [pseudo_metric_space α] {f : ℕ → α} (d : ℕ → ℝ)
  (hf : ∀ n, dist (f n) (f n.succ) ≤ d n) (hd : summable d) : cauchy_seq f :=
begin
  refine metric.cauchy_seq_iff'.2 (λε εpos, _),
  replace hd : cauchy_seq (λ (n : ℕ), ∑ x in range n, d x) :=
    let ⟨_, H⟩ := hd in H.tendsto_sum_nat.cauchy_seq,
  refine (metric.cauchy_seq_iff'.1 hd ε εpos).imp (λ N hN n hn, _),
  have hsum := hN n hn,
  rw [real.dist_eq, ← sum_Ico_eq_sub _ hn] at hsum,
  calc dist (f n) (f N) = dist (f N) (f n) : dist_comm _ _
  ... ≤ ∑ x in Ico N n, d x : dist_le_Ico_sum_of_dist_le hn (λ k _ _, hf k)
  ... ≤ |∑ x in Ico N n, d x| : le_abs_self _
  ... < ε : hsum
end

lemma cauchy_seq_of_summable_dist [pseudo_metric_space α] {f : ℕ → α}
  (h : summable (λn, dist (f n) (f n.succ))) : cauchy_seq f :=
cauchy_seq_of_dist_le_of_summable _ (λ _, le_refl _) h

lemma dist_le_tsum_of_dist_le_of_tendsto [pseudo_metric_space α] {f : ℕ → α} (d : ℕ → ℝ)
  (hf : ∀ n, dist (f n) (f n.succ) ≤ d n) (hd : summable d) {a : α} (ha : tendsto f at_top (𝓝 a))
  (n : ℕ) :
  dist (f n) a ≤ ∑' m, d (n + m) :=
begin
  refine le_of_tendsto (tendsto_const_nhds.dist ha)
    (eventually_at_top.2 ⟨n, λ m hnm, _⟩),
  refine le_trans (dist_le_Ico_sum_of_dist_le hnm (λ k _ _, hf k)) _,
  rw [sum_Ico_eq_sum_range],
  refine sum_le_tsum (range _) (λ _ _, le_trans dist_nonneg (hf _)) _,
  exact hd.comp_injective (add_right_injective n)
end

lemma dist_le_tsum_of_dist_le_of_tendsto₀ [pseudo_metric_space α] {f : ℕ → α} (d : ℕ → ℝ)
  (hf : ∀ n, dist (f n) (f n.succ) ≤ d n) (hd : summable d) {a : α} (ha : tendsto f at_top (𝓝 a)) :
  dist (f 0) a ≤ tsum d :=
by simpa only [zero_add] using dist_le_tsum_of_dist_le_of_tendsto d hf hd ha 0

lemma dist_le_tsum_dist_of_tendsto [pseudo_metric_space α] {f : ℕ → α}
  (h : summable (λn, dist (f n) (f n.succ))) {a : α} (ha : tendsto f at_top (𝓝 a)) (n) :
  dist (f n) a ≤ ∑' m, dist (f (n+m)) (f (n+m).succ) :=
show dist (f n) a ≤ ∑' m, (λx, dist (f x) (f x.succ)) (n + m), from
dist_le_tsum_of_dist_le_of_tendsto (λ n, dist (f n) (f n.succ)) (λ _, le_refl _) h ha n

lemma dist_le_tsum_dist_of_tendsto₀ [pseudo_metric_space α] {f : ℕ → α}
  (h : summable (λn, dist (f n) (f n.succ))) {a : α} (ha : tendsto f at_top (𝓝 a)) :
  dist (f 0) a ≤ ∑' n, dist (f n) (f n.succ) :=
by simpa only [zero_add] using dist_le_tsum_dist_of_tendsto h ha 0

end cauchy_seq

/-! ## Multipliying two infinite sums

In this section, we prove various results about `(∑' x : β, f x) * (∑' y : γ, g y)`. Note that we
always assume that the family `λ x : β × γ, f x.1 * g x.2` is summable, since there is no way to
deduce this from the summmabilities of `f` and `g` in general, but if you are working in a normed
space, you may want to use the analogous lemmas in `analysis/normed_space/basic`
(e.g `tsum_mul_tsum_of_summable_norm`).

We first establish results about arbitrary index types, `β` and `γ`, and then we specialize to
`β = γ = ℕ` to prove the Cauchy product formula (see `tsum_mul_tsum_eq_tsum_sum_antidiagonal`).

### Arbitrary index types
-/

section tsum_mul_tsum

variables [topological_space α] [regular_space α] [semiring α] [topological_ring α]
  {f : β → α} {g : γ → α} {s t u : α}

lemma has_sum.mul_eq (hf : has_sum f s) (hg : has_sum g t)
  (hfg : has_sum (λ (x : β × γ), f x.1 * g x.2) u) :
  s * t = u :=
have key₁ : has_sum (λ b, f b * t) (s * t),
  from hf.mul_right t,
have this : ∀ b : β, has_sum (λ c : γ, f b * g c) (f b * t),
  from λ b, hg.mul_left (f b),
have key₂ : has_sum (λ b, f b * t) u,
  from has_sum.prod_fiberwise hfg this,
key₁.unique key₂

lemma has_sum.mul (hf : has_sum f s) (hg : has_sum g t)
  (hfg : summable (λ (x : β × γ), f x.1 * g x.2)) :
  has_sum (λ (x : β × γ), f x.1 * g x.2) (s * t) :=
let ⟨u, hu⟩ := hfg in
(hf.mul_eq hg hu).symm ▸ hu

/-- Product of two infinites sums indexed by arbitrary types.
    See also `tsum_mul_tsum_of_summable_norm` if `f` and `g` are abolutely summable. -/
lemma tsum_mul_tsum (hf : summable f) (hg : summable g)
  (hfg : summable (λ (x : β × γ), f x.1 * g x.2)) :
  (∑' x, f x) * (∑' y, g y) = (∑' z : β × γ, f z.1 * g z.2) :=
hf.has_sum.mul_eq hg.has_sum hfg.has_sum

end tsum_mul_tsum

section cauchy_product

/-! ### `ℕ`-indexed families (Cauchy product)

We prove two versions of the Cauchy product formula. The first one is
`tsum_mul_tsum_eq_tsum_sum_range`, where the `n`-th term is a sum over `finset.range (n+1)`
involving `nat` substraction.
In order to avoid `nat` substraction, we also provide `tsum_mul_tsum_eq_tsum_sum_antidiagonal`,
where the `n`-th term is a sum over all pairs `(k, l)` such that `k+l=n`, which corresponds to the
`finset` `finset.nat.antidiagonal n` -/

variables {f : ℕ → α} {g : ℕ → α}

open finset

variables [topological_space α] [semiring α]

/- The family `(k, l) : ℕ × ℕ ↦ f k * g l` is summable if and only if the family
`(n, k, l) : Σ (n : ℕ), nat.antidiagonal n ↦ f k * g l` is summable. -/
lemma summable_mul_prod_iff_summable_mul_sigma_antidiagonal {f g : ℕ → α} :
  summable (λ x : ℕ × ℕ, f x.1 * g x.2) ↔
  summable (λ x : (Σ (n : ℕ), nat.antidiagonal n), f (x.2 : ℕ × ℕ).1 * g (x.2 : ℕ × ℕ).2) :=
nat.sigma_antidiagonal_equiv_prod.summable_iff.symm

variables [regular_space α] [topological_ring α]

lemma summable_sum_mul_antidiagonal_of_summable_mul {f g : ℕ → α}
  (h : summable (λ x : ℕ × ℕ, f x.1 * g x.2)) :
  summable (λ n, ∑ kl in nat.antidiagonal n, f kl.1 * g kl.2) :=
begin
  rw summable_mul_prod_iff_summable_mul_sigma_antidiagonal at h,
  conv {congr, funext, rw [← finset.sum_finset_coe, ← tsum_fintype]},
  exact h.sigma' (λ n, (has_sum_fintype _).summable),
end

/-- The Cauchy product formula for the product of two infinites sums indexed by `ℕ`,
    expressed by summing on `finset.nat.antidiagonal`.
    See also `tsum_mul_tsum_eq_tsum_sum_antidiagonal_of_summable_norm`
    if `f` and `g` are absolutely summable. -/
lemma tsum_mul_tsum_eq_tsum_sum_antidiagonal (hf : summable f) (hg : summable g)
  (hfg : summable (λ (x : ℕ × ℕ), f x.1 * g x.2)) :
  (∑' n, f n) * (∑' n, g n) = (∑' n, ∑ kl in nat.antidiagonal n, f kl.1 * g kl.2) :=
begin
  conv_rhs {congr, funext, rw [← finset.sum_finset_coe, ← tsum_fintype]},
  rw [tsum_mul_tsum hf hg hfg, ← nat.sigma_antidiagonal_equiv_prod.tsum_eq (_ : ℕ × ℕ → α)],
  exact tsum_sigma' (λ n, (has_sum_fintype _).summable)
    (summable_mul_prod_iff_summable_mul_sigma_antidiagonal.mp hfg)
end

lemma summable_sum_mul_range_of_summable_mul {f g : ℕ → α}
  (h : summable (λ x : ℕ × ℕ, f x.1 * g x.2)) :
  summable (λ n, ∑ k in range (n+1), f k * g (n - k)) :=
begin
  simp_rw ← nat.sum_antidiagonal_eq_sum_range_succ (λ k l, f k * g l),
  exact summable_sum_mul_antidiagonal_of_summable_mul h
end

/-- The Cauchy product formula for the product of two infinites sums indexed by `ℕ`,
    expressed by summing on `finset.range`.
    See also `tsum_mul_tsum_eq_tsum_sum_range_of_summable_norm`
    if `f` and `g` are absolutely summable. -/
lemma tsum_mul_tsum_eq_tsum_sum_range (hf : summable f) (hg : summable g)
  (hfg : summable (λ (x : ℕ × ℕ), f x.1 * g x.2)) :
  (∑' n, f n) * (∑' n, g n) = (∑' n, ∑ k in range (n+1), f k * g (n - k)) :=
begin
  simp_rw ← nat.sum_antidiagonal_eq_sum_range_succ (λ k l, f k * g l),
  exact tsum_mul_tsum_eq_tsum_sum_antidiagonal hf hg hfg
end

end cauchy_product


/-
Copyright (c) 2019 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro, Kevin Kappelmann
-/
import algebra.order.floor
import algebra.euclidean_domain.instances
import tactic.field_simp

/-!
# Floor Function for Rational Numbers

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

## Summary

We define the `floor` function and the `floor_ring` instance on `ℚ`. Some technical lemmas relating
`floor` to integer division and modulo arithmetic are derived as well as some simple inequalities.

## Tags

rat, rationals, ℚ, floor
-/

open int

namespace rat
variables {α : Type*} [linear_ordered_field α] [floor_ring α]

/-- `floor q` is the largest integer `z` such that `z ≤ q` -/
protected def floor : ℚ → ℤ
| ⟨n, d, h, c⟩ := n / d

protected theorem le_floor {z : ℤ} : ∀ {r : ℚ}, z ≤ rat.floor r ↔ (z : ℚ) ≤ r
| ⟨n, d, h, c⟩ := begin
  simp [rat.floor],
  rw [num_denom'],
  have h' := int.coe_nat_lt.2 h,
  conv { to_rhs,
    rw [coe_int_eq_mk, rat.le_def zero_lt_one h', mul_one] },
  exact int.le_div_iff_mul_le h'
end

instance : floor_ring ℚ :=
floor_ring.of_floor ℚ rat.floor $ λ a z, rat.le_floor.symm

protected lemma floor_def {q : ℚ} : ⌊q⌋ = q.num / q.denom := by { cases q, refl }

lemma floor_int_div_nat_eq_div {n : ℤ} {d : ℕ} : ⌊(↑n : ℚ) / (↑d : ℚ)⌋ = n / (↑d : ℤ) :=
begin
  rw [rat.floor_def],
  obtain rfl | hd := @eq_zero_or_pos _ _ d,
  { simp },
  set q := (n : ℚ) / d with q_eq,
  obtain ⟨c, n_eq_c_mul_num, d_eq_c_mul_denom⟩ : ∃ c, n = c * q.num ∧ (d : ℤ) = c * q.denom, by
  { rw q_eq,
    exact_mod_cast @rat.exists_eq_mul_div_num_and_eq_mul_div_denom n d (by exact_mod_cast hd.ne') },
  rw [n_eq_c_mul_num, d_eq_c_mul_denom],
  refine (int.mul_div_mul_of_pos _ _ $ pos_of_mul_pos_left _ $ int.coe_nat_nonneg q.denom).symm,
  rwa [←d_eq_c_mul_denom, int.coe_nat_pos],
end

@[simp, norm_cast] lemma floor_cast (x : ℚ) : ⌊(x : α)⌋ = ⌊x⌋ :=
floor_eq_iff.2 (by exact_mod_cast floor_eq_iff.1 (eq.refl ⌊x⌋))

@[simp, norm_cast] lemma ceil_cast (x : ℚ) : ⌈(x : α)⌉ = ⌈x⌉ :=
by rw [←neg_inj, ←floor_neg, ←floor_neg, ← rat.cast_neg, rat.floor_cast]

@[simp, norm_cast] lemma round_cast (x : ℚ) : round (x : α) = round x :=
have ((x + 1 / 2 : ℚ) : α) = x + 1 / 2, by simp,
by rw [round_eq, round_eq, ← this, floor_cast]

@[simp, norm_cast] lemma cast_fract (x : ℚ) : (↑(fract x) : α) = fract x :=
by simp only [fract, cast_sub, cast_coe_int, floor_cast]

end rat

lemma int.mod_nat_eq_sub_mul_floor_rat_div {n : ℤ} {d : ℕ} : n % d = n - d * ⌊(n : ℚ) / d⌋ :=
by rw [(eq_sub_of_add_eq $ int.mod_add_div n d), rat.floor_int_div_nat_eq_div]

lemma nat.coprime_sub_mul_floor_rat_div_of_coprime {n d : ℕ} (n_coprime_d : n.coprime d) :
  ((n : ℤ) - d * ⌊(n : ℚ)/ d⌋).nat_abs.coprime d :=
begin
  have : (n : ℤ) % d = n - d * ⌊(n : ℚ)/ d⌋, from int.mod_nat_eq_sub_mul_floor_rat_div,
  rw ←this,
  have : d.coprime n, from n_coprime_d.symm,
  rwa [nat.coprime, nat.gcd_rec] at this
end

namespace rat

lemma num_lt_succ_floor_mul_denom (q : ℚ) : q.num < (⌊q⌋ + 1) * q.denom :=
begin
  suffices : (q.num : ℚ) < (⌊q⌋ + 1) * q.denom, by exact_mod_cast this,
  suffices : (q.num : ℚ) < (q - fract q + 1) * q.denom, by
  { have : (⌊q⌋ : ℚ) = q - fract q, from (eq_sub_of_add_eq $ floor_add_fract q),
    rwa this },
  suffices : (q.num : ℚ) < q.num + (1 - fract q) * q.denom, by
  { have : (q - fract q + 1) * q.denom = q.num + (1 - fract q) * q.denom, calc
      (q - fract q + 1) * q.denom = (q + (1 - fract q)) * q.denom            : by ring
                              ... = q * q.denom + (1 - fract q) * q.denom    : by rw add_mul
                              ... = q.num + (1 - fract q) * q.denom : by simp,
    rwa this },
  suffices : 0 < (1 - fract q) * q.denom, by { rw ←sub_lt_iff_lt_add', simpa },
  have : 0 < 1 - fract q, by
  { have : fract q < 1, from fract_lt_one q,
    have : 0 + fract q < 1, by simp [this],
    rwa lt_sub_iff_add_lt },
  exact mul_pos this (by exact_mod_cast q.pos)
end

lemma fract_inv_num_lt_num_of_pos {q : ℚ} (q_pos : 0 < q): (fract q⁻¹).num < q.num :=
begin
  -- we know that the numerator must be positive
  have q_num_pos : 0 < q.num, from rat.num_pos_iff_pos.elim_right q_pos,
  -- we will work with the absolute value of the numerator, which is equal to the numerator
  have q_num_abs_eq_q_num : (q.num.nat_abs : ℤ) = q.num, from
    (int.nat_abs_of_nonneg q_num_pos.le),
  set q_inv := (q.denom : ℚ) / q.num with q_inv_def,
  have q_inv_eq : q⁻¹ = q_inv, from rat.inv_def',
  suffices : (q_inv - ⌊q_inv⌋).num < q.num, by rwa q_inv_eq,
  suffices : ((q.denom - q.num * ⌊q_inv⌋ : ℚ) / q.num).num < q.num, by
    field_simp [this, (ne_of_gt q_num_pos)],
  suffices : (q.denom : ℤ) - q.num * ⌊q_inv⌋ < q.num, by
  { -- use that `q.num` and `q.denom` are coprime to show that the numerator stays unreduced
    have : ((q.denom - q.num * ⌊q_inv⌋ : ℚ) / q.num).num = q.denom - q.num * ⌊q_inv⌋, by
    { suffices : ((q.denom : ℤ) - q.num * ⌊q_inv⌋).nat_abs.coprime q.num.nat_abs, by
        exact_mod_cast (rat.num_div_eq_of_coprime q_num_pos this),
      have tmp := nat.coprime_sub_mul_floor_rat_div_of_coprime q.cop.symm,
      simpa only [nat.cast_nat_abs, abs_of_nonneg q_num_pos.le] using tmp },
    rwa this },
  -- to show the claim, start with the following inequality
  have q_inv_num_denom_ineq : q⁻¹.num - ⌊q⁻¹⌋ * q⁻¹.denom < q⁻¹.denom, by
  { have : q⁻¹.num < (⌊q⁻¹⌋ + 1) * q⁻¹.denom, from rat.num_lt_succ_floor_mul_denom q⁻¹,
    have : q⁻¹.num < ⌊q⁻¹⌋ * q⁻¹.denom + q⁻¹.denom, by rwa [right_distrib, one_mul] at this,
    rwa [←sub_lt_iff_lt_add'] at this },
  -- use that `q.num` and `q.denom` are coprime to show that q_inv is the unreduced reciprocal
  -- of `q`
  have : q_inv.num = q.denom ∧ q_inv.denom = q.num.nat_abs, by
  { have coprime_q_denom_q_num : q.denom.coprime q.num.nat_abs, from q.cop.symm,
    have : int.nat_abs q.denom = q.denom, by simp,
    rw ←this at coprime_q_denom_q_num,
    rw q_inv_def,
    split,
    { exact_mod_cast (rat.num_div_eq_of_coprime q_num_pos coprime_q_denom_q_num) },
    { suffices : (((q.denom : ℚ) / q.num).denom : ℤ) = q.num.nat_abs, by exact_mod_cast this,
      rw q_num_abs_eq_q_num,
      exact_mod_cast (rat.denom_div_eq_of_coprime q_num_pos coprime_q_denom_q_num) } },
  rwa [q_inv_eq, this.left, this.right, q_num_abs_eq_q_num, mul_comm] at q_inv_num_denom_ineq
end

end rat


-- Eliminacion_de_la_implicacion.lean
-- Eliminación de la implicación.
-- José A. Alonso Jiménez
-- Sevilla, 12 de agosto de 2020
-- ---------------------------------------------------------------------

-- En este relación se muestra distintas formas de demostrar un teorema
-- con eliminación de la implicación.

-- ---------------------------------------------------------------------
-- Ejercicio. Realizar las siguientes acciones:
-- 1. Importar la librería de tácticas.
-- 2. Declarar P y Q como variables sobre proposiciones.
-- ----------------------------------------------------------------------

import tactic            -- 1
variables (P Q : Prop)   -- 2

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que si se tiene (P → Q) y P, entonces se tiene
-- Q.
-- ----------------------------------------------------------------------

-- 1ª demostración (hacia atrás)
-- =============================

example
  (h1 : P → Q)
  (h2 : P)
  : Q :=
begin
  apply h1,
  exact h2,
end

-- Prueba:
/-
  P Q : Prop,
  h1 : P → Q,
  h2 : P
  ⊢ Q
apply h1,
  ⊢ P
exact h2,
  no goals
-/

-- Comentarios:
-- + La táctica (apply h), cuando h es una implicación, aplica la regla
--   de eliminación de la implicación; es decir, si h es (P → Q) y la
--   conclusión coincide con Q, entonces sustituye la conclusión por P.

-- 2ª demostración (hacia adelante)
-- ================================

example
  (h1 : P → Q)
  (h2 : P)
  : Q :=
begin
  exact h1 h2,
end

-- Comentarios:
-- + Si h1 es una demostración de (P → Q) y h2 es una demostración de P,
--   entonces (h1 h2) es una demostración de Q.

-- 3ª demostración (simplificació de la 2ª)
-- ========================================

example
  (h1 : P → Q)
  (h2 : P)
  : Q :=
by exact h1 h2

-- 4ª demostración (mediante un término)
-- =====================================

example
  (h1 : P → Q)
  (h2 : P)
  : Q :=
h1 h2

-- 5ª demostración (automática con tauto)
-- ======================================

example
  (h1 : P → Q)
  (h2 : P)
  : Q :=
by tauto

-- Comentarios:
-- + La táctica tauto demuestra automáticamente las tautologías.

-- 6ª demostración (automática con finish)
-- =======================================

example
  (h1 : P → Q)
  (h2 : P)
  : Q :=
by finish

-- 6ª demostración (automática con solve_by_elim)
-- ==============================================

example
  (h1 : P → Q)
  (h2 : P)
  : Q :=
by solve_by_elim

-- Comentarios:
-- + La táctica solve_by_elim intnta demostrar el objetivo aplicándole
--   reglas de eliminación.


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

! This file was ported from Lean 3 source module combinatorics.simple_graph.strongly_regular
! leanprover-community/mathlib commit ee05e9ce1322178f0c12004eb93c00d2c8c00ed2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Combinatorics.SimpleGraph.Basic
import Mathbin.Data.Set.Finite

/-!
# Strongly regular graphs

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

## Main definitions

* `G.is_SRG_with n k ℓ μ` (see `simple_graph.is_SRG_with`) is a structure for
  a `simple_graph` satisfying the following conditions:
  * The cardinality of the vertex set is `n`
  * `G` is a regular graph with degree `k`
  * The number of common neighbors between any two adjacent vertices in `G` is `ℓ`
  * The number of common neighbors between any two nonadjacent vertices in `G` is `μ`

## TODO
- Prove that the parameters of a strongly regular graph
  obey the relation `(n - k - 1) * μ = k * (k - ℓ - 1)`
- Prove that if `I` is the identity matrix and `J` is the all-one matrix,
  then the adj matrix `A` of SRG obeys relation `A^2 = kI + ℓA + μ(J - I - A)`
-/

open Finset

universe u

namespace SimpleGraph

variable {V : Type u}

variable [Fintype V] [DecidableEq V]

variable (G : SimpleGraph V) [DecidableRel G.Adj]

/- warning: simple_graph.is_SRG_with -> SimpleGraph.IsSRGWith is a dubious translation:
lean 3 declaration is
  forall {V : Type.{u1}} [_inst_1 : Fintype.{u1} V] [_inst_2 : DecidableEq.{succ u1} V] (G : SimpleGraph.{u1} V) [_inst_3 : DecidableRel.{succ u1} V (SimpleGraph.Adj.{u1} V G)], Nat -> Nat -> Nat -> Nat -> Prop
but is expected to have type
  forall {V : Type.{u1}} [_inst_1 : Fintype.{u1} V] (_inst_2 : SimpleGraph.{u1} V) [G : DecidableRel.{succ u1} V (SimpleGraph.Adj.{u1} V _inst_2)], Nat -> Nat -> Nat -> Nat -> Prop
Case conversion may be inaccurate. Consider using '#align simple_graph.is_SRG_with SimpleGraph.IsSRGWithₓ'. -/
/-- A graph is strongly regular with parameters `n k ℓ μ` if
 * its vertex set has cardinality `n`
 * it is regular with degree `k`
 * every pair of adjacent vertices has `ℓ` common neighbors
 * every pair of nonadjacent vertices has `μ` common neighbors
-/
structure IsSRGWith (n k ℓ μ : ℕ) : Prop where
  card : Fintype.card V = n
  regular : G.IsRegularOfDegree k
  of_adj : ∀ v w : V, G.Adj v w → Fintype.card (G.commonNeighbors v w) = ℓ
  of_not_adj : ∀ v w : V, v ≠ w → ¬G.Adj v w → Fintype.card (G.commonNeighbors v w) = μ
#align simple_graph.is_SRG_with SimpleGraph.IsSRGWith

variable {G} {n k ℓ μ : ℕ}

/- warning: simple_graph.bot_strongly_regular -> SimpleGraph.bot_strongly_regular is a dubious translation:
lean 3 declaration is
  forall {V : Type.{u1}} [_inst_1 : Fintype.{u1} V] [_inst_2 : DecidableEq.{succ u1} V] {ℓ : Nat}, SimpleGraph.IsSRGWith.{u1} V _inst_1 (fun (a : V) (b : V) => _inst_2 a b) (Bot.bot.{u1} (SimpleGraph.{u1} V) (CompleteLattice.toHasBot.{u1} (SimpleGraph.{u1} V) (Order.Coframe.toCompleteLattice.{u1} (SimpleGraph.{u1} V) (CompleteDistribLattice.toCoframe.{u1} (SimpleGraph.{u1} V) (CompleteBooleanAlgebra.toCompleteDistribLattice.{u1} (SimpleGraph.{u1} V) (SimpleGraph.completeBooleanAlgebra.{u1} V)))))) (fun (a : V) (b : V) => SimpleGraph.Bot.adjDecidable.{u1} V a b) (Fintype.card.{u1} V _inst_1) (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))) ℓ (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))
but is expected to have type
  forall {V : Type.{u1}} [_inst_1 : Fintype.{u1} V] {_inst_2 : Nat}, SimpleGraph.IsSRGWith.{u1} V _inst_1 (Bot.bot.{u1} (SimpleGraph.{u1} V) (CompleteLattice.toBot.{u1} (SimpleGraph.{u1} V) (Order.Coframe.toCompleteLattice.{u1} (SimpleGraph.{u1} V) (CompleteDistribLattice.toCoframe.{u1} (SimpleGraph.{u1} V) (CompleteBooleanAlgebra.toCompleteDistribLattice.{u1} (SimpleGraph.{u1} V) (SimpleGraph.completeBooleanAlgebra.{u1} V)))))) (fun (a : V) (b : V) => SimpleGraph.Bot.adjDecidable.{u1} V a b) (Fintype.card.{u1} V _inst_1) (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)) _inst_2 (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))
Case conversion may be inaccurate. Consider using '#align simple_graph.bot_strongly_regular SimpleGraph.bot_strongly_regularₓ'. -/
/-- Empty graphs are strongly regular. Note that `ℓ` can take any value
  for empty graphs, since there are no pairs of adjacent vertices. -/
theorem bot_strongly_regular : (⊥ : SimpleGraph V).IsSRGWith (Fintype.card V) 0 ℓ 0 :=
  { card := rfl
    regular := bot_degree
    of_adj := fun v w h => h.elim
    of_not_adj := fun v w h =>
      by
      simp only [card_eq_zero, filter_congr_decidable, Fintype.card_ofFinset, forall_true_left,
        not_false_iff, bot_adj]
      ext
      simp [mem_common_neighbors] }
#align simple_graph.bot_strongly_regular SimpleGraph.bot_strongly_regular

#print SimpleGraph.IsSRGWith.top /-
/-- Complete graphs are strongly regular. Note that `μ` can take any value
  for complete graphs, since there are no distinct pairs of non-adjacent vertices. -/
theorem IsSRGWith.top :
    (⊤ : SimpleGraph V).IsSRGWith (Fintype.card V) (Fintype.card V - 1) (Fintype.card V - 2) μ :=
  { card := rfl
    regular := IsRegularOfDegree.top
    of_adj := fun v w h => by
      rw [card_common_neighbors_top]
      exact h
    of_not_adj := fun v w h h' => False.elim <| by simpa using h }
#align simple_graph.is_SRG_with.top SimpleGraph.IsSRGWith.top
-/

#print SimpleGraph.IsSRGWith.card_neighborFinset_union_eq /-
theorem IsSRGWith.card_neighborFinset_union_eq {v w : V} (h : G.IsSRGWith n k ℓ μ) :
    (G.neighborFinset v ∪ G.neighborFinset w).card = 2 * k - Fintype.card (G.commonNeighbors v w) :=
  by
  apply @Nat.add_right_cancel _ (Fintype.card (G.common_neighbors v w))
  rw [Nat.sub_add_cancel, ← Set.toFinset_card]
  ·
    simp [neighbor_finset, common_neighbors, Set.toFinset_inter, Finset.card_union_add_card_inter,
      h.regular.degree_eq, two_mul]
  · apply le_trans (card_common_neighbors_le_degree_left _ _ _)
    simp [h.regular.degree_eq, two_mul]
#align simple_graph.is_SRG_with.card_neighbor_finset_union_eq SimpleGraph.IsSRGWith.card_neighborFinset_union_eq
-/

#print SimpleGraph.IsSRGWith.card_neighborFinset_union_of_not_adj /-
/-- Assuming `G` is strongly regular, `2*(k + 1) - m` in `G` is the number of vertices that are
  adjacent to either `v` or `w` when `¬G.adj v w`. So it's the cardinality of
  `G.neighbor_set v ∪ G.neighbor_set w`. -/
theorem IsSRGWith.card_neighborFinset_union_of_not_adj {v w : V} (h : G.IsSRGWith n k ℓ μ)
    (hne : v ≠ w) (ha : ¬G.Adj v w) : (G.neighborFinset v ∪ G.neighborFinset w).card = 2 * k - μ :=
  by
  rw [← h.of_not_adj v w hne ha]
  apply h.card_neighbor_finset_union_eq
#align simple_graph.is_SRG_with.card_neighbor_finset_union_of_not_adj SimpleGraph.IsSRGWith.card_neighborFinset_union_of_not_adj
-/

#print SimpleGraph.IsSRGWith.card_neighborFinset_union_of_adj /-
theorem IsSRGWith.card_neighborFinset_union_of_adj {v w : V} (h : G.IsSRGWith n k ℓ μ)
    (ha : G.Adj v w) : (G.neighborFinset v ∪ G.neighborFinset w).card = 2 * k - ℓ :=
  by
  rw [← h.of_adj v w ha]
  apply h.card_neighbor_finset_union_eq
#align simple_graph.is_SRG_with.card_neighbor_finset_union_of_adj SimpleGraph.IsSRGWith.card_neighborFinset_union_of_adj
-/

#print SimpleGraph.compl_neighborFinset_sdiff_inter_eq /-
theorem compl_neighborFinset_sdiff_inter_eq {v w : V} :
    G.neighborFinset vᶜ \ {v} ∩ (G.neighborFinset wᶜ \ {w}) =
      (G.neighborFinset vᶜ ∩ G.neighborFinset wᶜ) \ ({w} ∪ {v}) :=
  by
  ext
  rw [← not_iff_not]
  simp [imp_iff_not_or, or_assoc', or_comm', or_left_comm]
#align simple_graph.compl_neighbor_finset_sdiff_inter_eq SimpleGraph.compl_neighborFinset_sdiff_inter_eq
-/

#print SimpleGraph.sdiff_compl_neighborFinset_inter_eq /-
theorem sdiff_compl_neighborFinset_inter_eq {v w : V} (h : G.Adj v w) :
    (G.neighborFinset vᶜ ∩ G.neighborFinset wᶜ) \ ({w} ∪ {v}) =
      G.neighborFinset vᶜ ∩ G.neighborFinset wᶜ :=
  by
  ext
  simp only [and_imp, mem_union, mem_sdiff, mem_compl, and_iff_left_iff_imp, mem_neighbor_finset,
    mem_inter, mem_singleton]
  rintro hnv hnw (rfl | rfl)
  · exact hnv h
  · apply hnw
    rwa [adj_comm]
#align simple_graph.sdiff_compl_neighbor_finset_inter_eq SimpleGraph.sdiff_compl_neighborFinset_inter_eq
-/

#print SimpleGraph.IsSRGWith.compl_is_regular /-
theorem IsSRGWith.compl_is_regular (h : G.IsSRGWith n k ℓ μ) : Gᶜ.IsRegularOfDegree (n - k - 1) :=
  by
  rw [← h.card, Nat.sub_sub, add_comm, ← Nat.sub_sub]
  exact h.regular.compl
#align simple_graph.is_SRG_with.compl_is_regular SimpleGraph.IsSRGWith.compl_is_regular
-/

#print SimpleGraph.IsSRGWith.card_commonNeighbors_eq_of_adj_compl /-
theorem IsSRGWith.card_commonNeighbors_eq_of_adj_compl (h : G.IsSRGWith n k ℓ μ) {v w : V}
    (ha : Gᶜ.Adj v w) : Fintype.card ↥(Gᶜ.commonNeighbors v w) = n - (2 * k - μ) - 2 :=
  by
  simp only [← Set.toFinset_card, common_neighbors, Set.toFinset_inter, neighbor_set_compl,
    Set.toFinset_diff, Set.toFinset_singleton, Set.toFinset_compl, ← neighbor_finset_def]
  simp_rw [compl_neighbor_finset_sdiff_inter_eq]
  have hne : v ≠ w := ne_of_adj _ ha
  rw [compl_adj] at ha
  rw [card_sdiff, ← insert_eq, card_insert_of_not_mem, card_singleton, ← Finset.compl_union]
  · change 1 + 1 with 2
    rw [card_compl, h.card_neighbor_finset_union_of_not_adj hne ha.2, ← h.card]
  · simp only [hne.symm, not_false_iff, mem_singleton]
  · intro u
    simp only [mem_union, mem_compl, mem_neighbor_finset, mem_inter, mem_singleton]
    rintro (rfl | rfl) <;> simpa [adj_comm] using ha.2
#align simple_graph.is_SRG_with.card_common_neighbors_eq_of_adj_compl SimpleGraph.IsSRGWith.card_commonNeighbors_eq_of_adj_compl
-/

#print SimpleGraph.IsSRGWith.card_commonNeighbors_eq_of_not_adj_compl /-
theorem IsSRGWith.card_commonNeighbors_eq_of_not_adj_compl (h : G.IsSRGWith n k ℓ μ) {v w : V}
    (hn : v ≠ w) (hna : ¬Gᶜ.Adj v w) : Fintype.card ↥(Gᶜ.commonNeighbors v w) = n - (2 * k - ℓ) :=
  by
  simp only [← Set.toFinset_card, common_neighbors, Set.toFinset_inter, neighbor_set_compl,
    Set.toFinset_diff, Set.toFinset_singleton, Set.toFinset_compl, ← neighbor_finset_def]
  simp only [not_and, Classical.not_not, compl_adj] at hna
  have h2' := hna hn
  simp_rw [compl_neighbor_finset_sdiff_inter_eq, sdiff_compl_neighbor_finset_inter_eq h2']
  rwa [← Finset.compl_union, card_compl, h.card_neighbor_finset_union_of_adj, ← h.card]
#align simple_graph.is_SRG_with.card_common_neighbors_eq_of_not_adj_compl SimpleGraph.IsSRGWith.card_commonNeighbors_eq_of_not_adj_compl
-/

#print SimpleGraph.IsSRGWith.compl /-
/-- The complement of a strongly regular graph is strongly regular. -/
theorem IsSRGWith.compl (h : G.IsSRGWith n k ℓ μ) :
    Gᶜ.IsSRGWith n (n - k - 1) (n - (2 * k - μ) - 2) (n - (2 * k - ℓ)) :=
  { card := h.card
    regular := h.compl_is_regular
    of_adj := fun v w ha => h.card_commonNeighbors_eq_of_adj_compl ha
    of_not_adj := fun v w hn hna => h.card_commonNeighbors_eq_of_not_adj_compl hn hna }
#align simple_graph.is_SRG_with.compl SimpleGraph.IsSRGWith.compl
-/

end SimpleGraph


/-
Copyright (c) 2023 Jujian Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Jujian Zhang
-/

import ring_theory.ideal.operations
import topology.algebra.polynomial
import topology.bases

/-!
# Prime spectrum of a ring and its Zariski topology

This files contains the following:
- Zariski topology on the set of all prime ideals of any ring `R`.
- a basis for Zariski topology
- if `f : R →+* S` is a ring homomorphism, then `𝔭 ↦ f⁻¹ 𝔭` is continuous.
- for integral domains, there is a unique generic point.
-/

open topological_space
open_locale pointwise

universe u

variables (R S : Type u) [comm_ring R] [comm_ring S]

/--
`Spec R` is the set of prime ideals of `R`
-/
@[ext]
structure Spec : Type u :=
(as_ideal : ideal R)
(is_prime : as_ideal.is_prime)

attribute [instance] Spec.is_prime -- making sure class inference knows term of `Spec R` is prime

section

variable {R}

/--
zero locus of a set `s ⊆ R` is the set of all prime ideals larger than `s`.

if `f : R`, then it defines a function `𝔭 ↦ ([f] : R ⧸ 𝔭)`.

So `V s` is exactly those primes
vanishing for all `f ∈ s`.
-/
def V (s : set R) : set (Spec R) :=
{ I : Spec R | s ⊆ I.as_ideal }

lemma mem_V (s : set R) {p : Spec R} : p ∈ V s ↔ s ⊆ p.as_ideal :=
iff.rfl

/--
empty set is zero locus of `R`
-/
lemma V_univ : V (set.univ : set R) = ∅ :=
begin
  rw set.eq_empty_iff_forall_not_mem,
  intros p,
  rw [mem_V, set.univ_subset_iff],
  have mem1 : (1 : R) ∉ (p.as_ideal : set R) := p.as_ideal.ne_top_iff_one.mp p.is_prime.ne_top,
  intros r,
  rw r at mem1,
  exact mem1 ⟨⟩,
end

/--
R is zero locus of `∅`
-/
lemma V_empty : V (∅ : set R) = set.univ :=
set.eq_univ_iff_forall.mpr $ λ p x r, false.elim $ (set.mem_empty_iff_false _).mp r

/--
union of zero loci is zero locus of pointwise product
-/
lemma V_union (s t : set R) : V s ∪ V t = V (s * t) :=
begin
  ext p,
  split,
  { rintros (hp|hp);
    rw mem_V at hp ⊢;
    intros r hr;
    obtain ⟨a, b, ha, hb, rfl⟩ := hr,
    { specialize hp ha,
      exact p.as_ideal.mul_mem_right _ hp, },
    { specialize hp hb,
      exact p.as_ideal.mul_mem_left _ hp, }, },
  { intros hp,
    rw mem_V at hp,
    rw [set.mem_union, mem_V, mem_V],
    contrapose! hp,
    simp only [set.not_subset_iff_exists_mem_not_mem] at hp ⊢,
    obtain ⟨⟨a, ha1, ha2⟩, ⟨b, hb1, hb2⟩⟩ := hp,
    exact ⟨_, ⟨a, b, ha1, hb1, rfl⟩, λ r, (p.is_prime.mem_or_mem r).elim
      (λ h, ha2 h) (λ h, hb2 h)⟩, },
end

/--
intersection of zero loci is zero locus of union
-/
lemma V_inter {ι : Sort*} (s : ι → set R) :
  (⋂ i : ι, V (s i)) = V (⋃ i, (s i)) :=
begin
  ext p,
  split;
  intros hp,
  { simp_rw [set.mem_Inter, mem_V] at hp,
    rw [mem_V, set.Union_subset_iff],
    assumption, },
  { rw [mem_V, set.Union_subset_iff] at hp,
    simp_rw [set.mem_Inter, mem_V],
    assumption, },
end

end

instance Zariski_topology : topological_space (Spec R) :=
topological_space.of_closed
{ t | ∃ (s : set R), t = V s } ⟨set.univ, V_univ.symm⟩
begin
  rintros _ hS,
  choose a ha using hS,
  rw [set.sInter_eq_bInter],
  suffices : (⋂ (i : set (Spec R)) (h : i ∈ A), i) = ⋂ (i : A), V (a i.2),
  { rw [this, V_inter],
    exact ⟨_, rfl⟩, },
  simp only [subtype.val_eq_coe, subtype.coe_mk, set.Inter_coe_set],
  exact set.Inter_congr (λ s, set.Inter_congr $ λ hs, ha hs),
end
begin
  rintros _ ⟨s, rfl⟩ _ ⟨t, rfl⟩,
  rw V_union,
  exact ⟨s * t, rfl⟩,
end

/--
open sets of Zariski topology are complement of zero loci
-/
lemma zt_is_open (s : set (Spec R)) : is_open s ↔ ∃ (t : set R), s = (V t)ᶜ :=
begin
  change (∃ _, _) ↔ _,
  rw [exists_congr],
  exact λ a, by rw [eq_compl_comm, eq_comm],
end

section

variables {R S}

/--
Basic open sets
-/
def D (f : R) : set (Spec R) := (V {f})ᶜ

lemma mem_D (f : R) (p : Spec R) : p ∈ D f ↔ f ∉ p.as_ideal :=
begin
  split;
  intros hp hf;
  rw [D, set.mem_compl_iff, mem_V, set.singleton_subset_iff] at hp <|>
  rw [mem_V, set.singleton_subset_iff] at hf;
  exact hp hf,
end

lemma open_D (f : R) : is_open (D f) :=
begin
  rw [D, zt_is_open],
  exact ⟨_, rfl⟩,
end

/--
Basic open sets form a basis
-/
lemma D_is_basis : is_topological_basis (set.range (D : R → set (Spec R))) :=
is_topological_basis_of_open_of_nhds (by { rintros _ ⟨r, -, rfl⟩, exact open_D _ }) $
λ p s hp hs, begin
  simp only [set.mem_range, exists_prop, exists_exists_eq_and, mem_D],
  rw zt_is_open at hs,
  obtain ⟨s, rfl⟩ := hs,
  rw [set.mem_compl_iff, mem_V, set.not_subset_iff_exists_mem_not_mem] at hp,
  obtain ⟨x, hx1, hx2⟩ := hp,
  refine ⟨x, hx2, λ y hy H, _⟩,
  exact (mem_D _ _).mp hy (H hx1),
end

/--
Ring homomorphisms induces continuous map (contravariantly).
-/
def comap (f : R →+* S) : Spec S → Spec R :=
λ p, ⟨p.as_ideal.comap f, infer_instance⟩

lemma comap_as_ideal (f : R →+* S) (p : Spec S) : (comap f p).as_ideal = p.as_ideal.comap f := rfl

lemma continuous_comap (f : R →+* S) : continuous (comap f) :=
begin
  refine D_is_basis.continuous _ _,
  rintros _ ⟨r, rfl⟩,
  rw show comap f ⁻¹' D r  = D (f r),
  { ext1 p,
    simp only [set.mem_preimage, mem_D, comap_as_ideal, ideal.mem_comap], },
  exact open_D _,
end

local notation `ℤ[X]` := (polynomial ℤ)
-- every thing from this points work for a generic integral domain
/--
the point corresponding to the zero ideal.
-/
@[simps]
noncomputable def η : Spec ℤ[X] :=
{ as_ideal := ⊥,
  is_prime :=
  { ne_top' := begin
      rw [ideal.ne_top_iff_one, ideal.mem_bot],
      norm_num,
    end,
    mem_or_mem' := λ x y hxy,
    begin
      simp only [ideal.mem_bot] at hxy ⊢,
      rwa mul_eq_zero at hxy,
    end } }

/--
this is a generic point.
-/
lemma generic_η : closure {η} = (set.univ : set (Spec ℤ[X])) :=
begin
  rw show closure {η} = V (η.as_ideal : set ℤ[X]),
  { ext,
    rw [mem_V, mem_closure_iff, η_as_ideal],
    split,
    { intros h r hr,
      rw [set_like.mem_coe, ideal.mem_bot] at hr,
      rw hr,
      exact ideal.zero_mem _, },
    { rintros - o ho hx,
      rw zt_is_open at ho,
      obtain ⟨o, rfl⟩ := ho,
      rw [set.mem_compl_iff, mem_V, set.not_subset_iff_exists_mem_not_mem] at hx,
      obtain ⟨q, hq1, hq2⟩ := hx,
      by_cases q0 : q = 0,
      { rw [q0] at *,
        exfalso,
        refine hq2 (ideal.zero_mem _), },
      rw show (V o)ᶜ ∩ {η} = {η},
      { rw [set.inter_eq_right_iff_subset, set.singleton_subset_iff,
          set.mem_compl_iff, mem_V, set.not_subset_iff_exists_mem_not_mem, η_as_ideal],
        exact ⟨q, hq1, q0⟩, },
      exact ⟨η, set.mem_singleton _⟩, }, },
  rw [η_as_ideal, set.eq_univ_iff_forall],
  intros x,
  rw [mem_V],
  intros y hy,
  rw [set_like.mem_coe, ideal.mem_bot] at hy,
  rw hy,
  exact ideal.zero_mem _,
end

/--
Generic points is unique.
-/
lemma generic_point_uniq (x : Spec ℤ[X]) (hx : closure {x} = (set.univ : set (Spec ℤ[X]))) :
  x = η :=
begin
  have h : closure {x} = closure {η},
  { rw [generic_η, hx], },
  have H : ∀ (a b : Spec ℤ[X]), a.as_ideal ≤ b.as_ideal ↔ b ∈ closure {a},
  { intros a b,
    split,
    { rw mem_closure_iff,
      intros hle o ho hb,
      rw zt_is_open at ho,
      obtain ⟨o, rfl⟩ := ho,
      rw [set.mem_compl_iff, mem_V, set.not_subset_iff_exists_mem_not_mem] at hb,
      obtain ⟨q, hq1, hq2⟩ := hb,
      rw [set.inter_nonempty],
      simp_rw [set.mem_compl_iff, mem_V, set.not_subset_iff_exists_mem_not_mem,
        set_like.mem_coe],
      exact ⟨a, ⟨q, hq1, λ h, hq2 (hle h)⟩, set.mem_singleton _⟩, },
    { intros hmem p hp,
      rw [mem_closure_iff] at hmem,
      contrapose! hp,
      specialize hmem (D p) (open_D p) ((mem_D _ _).mpr hp),
      obtain ⟨x, hx1, hx2⟩ := hmem,
      rw set.mem_singleton_iff at hx2,
      rw hx2 at *,
      rwa mem_D at hx1, }, },
  have Hle1 : x.as_ideal ≤ η.as_ideal,
  { rw [H, h],
    exact subset_closure (set.mem_singleton _), },
  have Hle2 : η.as_ideal ≤ x.as_ideal,
  { rw [H, ←h],
    exact subset_closure (set.mem_singleton _), },
  ext1,
  exact le_antisymm Hle1 Hle2,
end

end


/-
Copyright © 2018 François G. Dorais. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
-/

namespace nat
variables {m n : ℕ}

abbreviation add_sub_cancel_right := nat.add_sub_cancel

lemma sub_add_of_le {m n : ℕ} : m ≤ n → (n - m) + m = n :=
assume h : m ≤ n, by rw [add_comm, add_sub_of_le h]

lemma sub_lt_of_lt_add_of_ge {m n i : ℕ} : i ≥ m → i < m + n → i - m < n :=
assume (hm : m ≤ i) (h : i < m + n),
lt_of_add_lt_add_left $
show m + (i - m) < m + n,
from calc m + (i - m) = i : add_sub_of_le hm
              ... < m + n : h

lemma le_sub_of_add_le_of_ge {m n i : ℕ} :
n ≤ i → n + m ≤ i → m ≤ i - n :=
assume hn h,
le_of_add_le_add_left $
show n + m ≤ n + (i - n),
from calc n + m ≤ i : h
  ... = n + (i - n) : eq.symm $ add_sub_of_le hn

inductive nonzero (n : ℕ) : Prop
| ne_zero : n ≠ 0 → nonzero
| zero_ne : 0 ≠ n → nonzero

lemma ne_zero_of_nonzero : nonzero n → n ≠ 0
| (nonzero.ne_zero h) := h
| (nonzero.zero_ne h) := ne.symm h

lemma zero_ne_of_nonzero : nonzero n → 0 ≠ n
| (nonzero.ne_zero h) := ne.symm h
| (nonzero.zero_ne h) := h

lemma zero_lt_of_zero_ne : 0 ≠ n → 0 < n := lt_of_le_of_ne (zero_le n)
lemma zero_lt_of_ne_zero : n ≠ 0 → 0 < n := λ h, zero_lt_of_zero_ne (ne.symm h)

lemma zero_ne_of_zero_ne_mul_left : 0 ≠ m * n → 0 ≠ m :=
mt $ λ h, eq.rec_on h (eq.symm $ zero_mul n)
lemma ne_zero_of_mul_ne_zero_left : m * n ≠ 0 → m ≠ 0 :=
mt $ λ h, eq.rec_on (eq.symm h) (zero_mul n)
lemma zero_lt_of_zero_lt_mul_left : 0 < m * n → 0 < m :=
λ h, zero_lt_of_zero_ne (zero_ne_of_zero_ne_mul_left (ne_of_lt h))

lemma zero_ne_of_zero_ne_mul_right : 0 ≠ m * n → 0 ≠ n :=
mt $ λ h, eq.rec_on h (eq.symm $ mul_zero m)
lemma ne_zero_of_mul_ne_zero_right : m * n ≠ 0 → n ≠ 0 :=
mt $ λ h, eq.rec_on (eq.symm h) (mul_zero m)
lemma zero_lt_of_zero_lt_mul_right : 0 < m * n → 0 < n :=
λ h, zero_lt_of_zero_ne (zero_ne_of_zero_ne_mul_right (ne_of_lt h))

lemma div_alg {q r : ℕ} : r < m → (n = r + m * q ↔ q = n / m ∧ r = n % m) :=
begin
intro hr,
have hm : 0 < m, from lt_of_le_of_lt (zero_le r) hr,
split,
{ intro hn,
  split,
  rw [hn, nat.add_mul_div_left _ _ hm, nat.div_eq_of_lt hr, zero_add],
  rw [hn, nat.add_mul_mod_self_left, nat.mod_eq_of_lt hr],
},
{ intro h, destruct h, intros hq hr,
  rw [hq, hr, nat.mod_add_div],
},
end

end nat


import
  tactic.find tactic.omega

lemma zip_with_len_l {α β γ : Type*} {l₁ : list α} {l₂ : list β} {f : α → β → γ}
  (h : list.length l₁ = list.length l₂) :
  list.length (list.zip_with f l₁ l₂) = list.length l₁ :=
begin
  induction l₁ with x xs ih generalizing l₂,
    {simp [list.zip_with]},
    {
      cases l₂ with y ys,
        {injection h},
        {simp only [list.zip_with, list.length], finish}
    }
end

lemma map_with_len_l {α β : Type*} {l₁ : list α} {f : α → β} :
  list.length (list.map f l₁) = list.length l₁ :=
begin
  induction l₁ with x xs ih,
  {simp [list.map]},
  {simp [list.map]},
end


/-
Copyright (c) 2021 Damiano Testa. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Damiano Testa, Jens Wagemaker
-/
import data.nat.interval
import data.polynomial.degree.definitions

/-!
# Induction on polynomials

This file contains lemmas dealing with different flavours of induction on polynomials.
-/

noncomputable theory
open_locale classical big_operators

open finset

namespace polynomial
universes u v w z
variables {R : Type u} {S : Type v} {T : Type w} {A : Type z} {a b : R} {n : ℕ}

section semiring
variables [semiring R] {p q : polynomial R}

/-- `div_X p` returns a polynomial `q` such that `q * X + C (p.coeff 0) = p`.
  It can be used in a semiring where the usual division algorithm is not possible -/
def div_X (p : polynomial R) : polynomial R :=
∑ n in Ico 0 p.nat_degree, monomial n (p.coeff (n + 1))

@[simp] lemma coeff_div_X : (div_X p).coeff n = p.coeff (n+1) :=
begin
  simp only [div_X, coeff_monomial, true_and, finset_sum_coeff, not_lt,
    mem_Ico, zero_le, finset.sum_ite_eq', ite_eq_left_iff],
  intro h,
  rw coeff_eq_zero_of_nat_degree_lt (nat.lt_succ_of_le h)
end

lemma div_X_mul_X_add (p : polynomial R) : div_X p * X + C (p.coeff 0) = p :=
ext $ by rintro ⟨_|_⟩; simp [coeff_C, nat.succ_ne_zero, coeff_mul_X]

@[simp] lemma div_X_C (a : R) : div_X (C a) = 0 :=
ext $ λ n, by cases n; simp [div_X, coeff_C]; simp [coeff]

lemma div_X_eq_zero_iff : div_X p = 0 ↔ p = C (p.coeff 0) :=
⟨λ h, by simpa [eq_comm, h] using div_X_mul_X_add p,
  λ h, by rw [h, div_X_C]⟩

lemma div_X_add : div_X (p + q) = div_X p + div_X q :=
ext $ by simp

lemma degree_div_X_lt (hp0 : p ≠ 0) : (div_X p).degree < p.degree :=
by haveI := nontrivial.of_polynomial_ne hp0;
calc (div_X p).degree < (div_X p * X + C (p.coeff 0)).degree :
  if h : degree p ≤ 0
  then begin
      have h' : C (p.coeff 0) ≠ 0, by rwa [← eq_C_of_degree_le_zero h],
      rw [eq_C_of_degree_le_zero h, div_X_C, degree_zero, zero_mul, zero_add],
      exact lt_of_le_of_ne bot_le (ne.symm (mt degree_eq_bot.1 $
        by simp [h'])),
    end
  else
    have hXp0 : div_X p ≠ 0,
      by simpa [div_X_eq_zero_iff, -not_le, degree_le_zero_iff] using h,
    have leading_coeff (div_X p) * leading_coeff X ≠ 0, by simpa,
    have degree (C (p.coeff 0)) < degree (div_X p * X),
      from calc degree (C (p.coeff 0)) ≤ 0 : degree_C_le
         ... < 1 : dec_trivial
         ... = degree (X : polynomial R) : degree_X.symm
         ... ≤ degree (div_X p * X) :
          by rw [← zero_add (degree X), degree_mul' this];
            exact add_le_add
              (by rw [zero_le_degree_iff, ne.def, div_X_eq_zero_iff];
                exact λ h0, h (h0.symm ▸ degree_C_le))
              (le_refl _),
    by rw [degree_add_eq_left_of_degree_lt this];
      exact degree_lt_degree_mul_X hXp0
... = p.degree : congr_arg _ (div_X_mul_X_add _)

/-- An induction principle for polynomials, valued in Sort* instead of Prop. -/
@[elab_as_eliminator] noncomputable def rec_on_horner
  {M : polynomial R → Sort*} : Π (p : polynomial R),
  M 0 →
  (Π p a, coeff p 0 = 0 → a ≠ 0 → M p → M (p + C a)) →
  (Π p, p ≠ 0 → M p → M (p * X)) →
  M p
| p := λ M0 MC MX,
if hp : p = 0 then eq.rec_on hp.symm M0
else
have wf : degree (div_X p) < degree p,
  from degree_div_X_lt hp,
by rw [← div_X_mul_X_add p] at *;
  exact
  if hcp0 : coeff p 0 = 0
  then by rw [hcp0, C_0, add_zero];
    exact MX _ (λ h : div_X p = 0, by simpa [h, hcp0] using hp)
      (rec_on_horner _ M0 MC MX)
  else MC _ _ (coeff_mul_X_zero _) hcp0 (if hpX0 : div_X p = 0
    then show M (div_X p * X), by rw [hpX0, zero_mul]; exact M0
    else MX (div_X p) hpX0 (rec_on_horner _ M0 MC MX))
using_well_founded {dec_tac := tactic.assumption}

/--  A property holds for all polynomials of positive `degree` with coefficients in a semiring `R`
if it holds for
* `a * X`, with `a ∈ R`,
* `p * X`, with `p ∈ R[X]`,
* `p + a`, with `a ∈ R`, `p ∈ R[X]`,
with appropriate restrictions on each term.

See `nat_degree_ne_zero_induction_on` for a similar statement involving no explicit multiplication.
 -/
@[elab_as_eliminator] lemma degree_pos_induction_on
  {P : polynomial R → Prop} (p : polynomial R) (h0 : 0 < degree p)
  (hC : ∀ {a}, a ≠ 0 → P (C a * X))
  (hX : ∀ {p}, 0 < degree p → P p → P (p * X))
  (hadd : ∀ {p} {a}, 0 < degree p → P p → P (p + C a)) : P p :=
rec_on_horner p
  (λ h, by rw degree_zero at h; exact absurd h dec_trivial)
  (λ p a _ _ ih h0,
    have 0 < degree p,
      from lt_of_not_ge (λ h, (not_lt_of_ge degree_C_le) $
        by rwa [eq_C_of_degree_le_zero h, ← C_add] at h0),
    hadd this (ih this))
  (λ p _ ih h0',
    if h0 : 0 < degree p
    then hX h0 (ih h0)
    else by rw [eq_C_of_degree_le_zero (le_of_not_gt h0)] at *;
      exact hC (λ h : coeff p 0 = 0,
        by simpa [h, nat.not_lt_zero] using h0'))
  h0

end semiring

end polynomial


import neater_proof.rings_and_properties

local infixr ` + ` : 80 := plus
local infixr ` * ` : 80 := mult

/-- As with the definition of Ring, this is standard, see, e.g.:
    https://en.wikipedia.org/wiki/Ideal_(ring_theory) -/
structure Ideal (R : Ring) :=
(I : set R.R)
(ideal_axioms : (zero ∈ I
               ∧ (∀ x y : R.R, x ∈ I → y ∈ I → x + y ∈ I)
               ∧ (∀ x x' : R.R, x + x' = zero → x ∈ I → x' ∈ I))
              ∧ ((∀ x y : R.R, x ∈ I → x * y ∈ I)
               ∧ (∀ x y : R.R, x ∈ I → y * x ∈ I)))

def subgroup_under_addition {R : Ring} (I : Ideal R) :=
I.ideal_axioms.1

def multiplication_conditions {R : Ring} (I : Ideal R) :=
I.ideal_axioms.2

def zero_mem_Ideal {R : Ring} (I : Ideal R) : zero ∈ I.I :=
(subgroup_under_addition I).1

-- i am undecided about whether or not to add more definitions for the various axioms

/-- This result is fundamental to the proof, and will be used for every sub-lemma of the main
    theorem. It tells us that it is sufficient, for two Ideals to be identical, that their
    underlying sets are identical; this is essentially a consequence of a feature of lean called
    'proof irrelevance' - if two proofs prove the same proposition, then they are equal. -/
lemma ideal_equality_condition {R : Ring} : ∀ I I' : Ideal R, I.I = I'.I → I = I' :=
begin
 intros I I' h,
 have h' : ((zero ∈ I.I
         ∧ (∀ x y : R.R, x ∈ I.I → y ∈ I.I → x + y ∈ I.I)
         ∧ (∀ x x' : R.R, x + x' = zero → x ∈ I.I → x' ∈ I.I))
        ∧ ((∀ x y : R.R, x ∈ I.I → x * y ∈ I.I)
         ∧ (∀ x y : R.R, x ∈ I.I → y * x ∈ I.I)))
                               ↔
          ((zero ∈ I'.I
         ∧ (∀ x y : R.R, x ∈ I'.I → y ∈ I'.I → x + y ∈ I'.I)
         ∧ (∀ x x' : R.R, x + x' = zero → x ∈ I'.I → x' ∈ I'.I))
        ∧ ((∀ x y : R.R, x ∈ I'.I → x * y ∈ I'.I)
         ∧ (∀ x y : R.R, x ∈ I'.I → y * x ∈ I'.I))),
 { split,
   { intro h',
     rw ← h,
     exact h', },
   { intro h',
     rw h,
     exact h' },
 },
 have h'' := propext h',
 cases I,
 cases I',
 congr,
 assumption,
end