/-
Copyright (c) 2022 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/
import algebra.is_prime_pow
import data.nat.factorization.basic

/-!
# Prime powers and factorizations

This file deals with factorizations of prime powers.
-/

variables {R : Type*} [comm_monoid_with_zero R] (n p : R) (k : ℕ)

lemma is_prime_pow.min_fac_pow_factorization_eq {n : ℕ} (hn : is_prime_pow n) :
  n.min_fac ^ n.factorization n.min_fac = n :=
begin
  obtain ⟨p, k, hp, hk, rfl⟩ := hn,
  rw ←nat.prime_iff at hp,
  rw [hp.pow_min_fac hk.ne', hp.factorization_pow, finsupp.single_eq_same],
end

lemma is_prime_pow_of_min_fac_pow_factorization_eq {n : ℕ}
  (h : n.min_fac ^ n.factorization n.min_fac = n) (hn : n ≠ 1) :
  is_prime_pow n :=
begin
  rcases eq_or_ne n 0 with rfl | hn',
  { simpa using h },
  refine ⟨_, _, (nat.min_fac_prime hn).prime, _, h⟩,
  rw [pos_iff_ne_zero, ←finsupp.mem_support_iff, nat.factor_iff_mem_factorization,
    nat.mem_factors_iff_dvd hn' (nat.min_fac_prime hn)],
  apply nat.min_fac_dvd
end

lemma is_prime_pow_iff_min_fac_pow_factorization_eq {n : ℕ} (hn : n ≠ 1) :
  is_prime_pow n ↔ n.min_fac ^ n.factorization n.min_fac = n :=
⟨λ h, h.min_fac_pow_factorization_eq, λ h, is_prime_pow_of_min_fac_pow_factorization_eq h hn⟩

lemma is_prime_pow_iff_factorization_eq_single {n : ℕ} :
  is_prime_pow n ↔ ∃ p k : ℕ, 0 < k ∧ n.factorization = finsupp.single p k :=
begin
  rw is_prime_pow_nat_iff,
  refine exists₂_congr (λ p k, _),
  split,
  { rintros ⟨hp, hk, hn⟩,
    exact ⟨hk, by rw [←hn, nat.prime.factorization_pow hp]⟩ },
  { rintros ⟨hk, hn⟩,
    have hn0 : n ≠ 0,
    { rintro rfl,
      simpa only [finsupp.single_eq_zero, eq_comm, nat.factorization_zero, hk.ne'] using hn },
    rw nat.eq_pow_of_factorization_eq_single hn0 hn,
    exact ⟨nat.prime_of_mem_factorization
      (by simp [hn, hk.ne'] : p ∈ n.factorization.support), hk, rfl⟩ }
end

lemma is_prime_pow_iff_card_support_factorization_eq_one {n : ℕ} :
  is_prime_pow n ↔ n.factorization.support.card = 1 :=
by simp_rw [is_prime_pow_iff_factorization_eq_single, finsupp.card_support_eq_one', exists_prop,
  pos_iff_ne_zero]

lemma is_prime_pow.exists_ord_compl_eq_one {n : ℕ} (h : is_prime_pow n) :
  ∃ p : ℕ, p.prime ∧ ord_compl[p] n = 1 :=
begin
  rcases eq_or_ne n 0 with rfl | hn0, { cases not_is_prime_pow_zero h },
  rcases is_prime_pow_iff_factorization_eq_single.mp h with ⟨p, k, hk0, h1⟩,
  rcases em' p.prime with pp | pp,
  { refine absurd _ hk0.ne', simp [←nat.factorization_eq_zero_of_non_prime n pp, h1] },
  refine ⟨p, pp, _⟩,
  refine nat.eq_of_factorization_eq (nat.ord_compl_pos p hn0).ne' (by simp) (λ q, _),
  rw [nat.factorization_ord_compl n p, h1],
  simp,
end

lemma exists_ord_compl_eq_one_iff_is_prime_pow {n : ℕ} (hn : n ≠ 1) :
  is_prime_pow n ↔ ∃ p : ℕ, p.prime ∧ ord_compl[p] n = 1 :=
begin
  refine ⟨λ h, is_prime_pow.exists_ord_compl_eq_one h, λ h, _⟩,
  rcases h with ⟨p, pp, h⟩,
  rw is_prime_pow_nat_iff,
  rw [←nat.eq_of_dvd_of_div_eq_one (nat.ord_proj_dvd n p) h] at ⊢ hn,
  refine ⟨p, n.factorization p, pp, _, by simp⟩,
  contrapose! hn,
  simp [le_zero_iff.1 hn],
end

/-- An equivalent definition for prime powers: `n` is a prime power iff there is a unique prime
dividing it. -/
lemma is_prime_pow_iff_unique_prime_dvd {n : ℕ} :
  is_prime_pow n ↔ ∃! p : ℕ, p.prime ∧ p ∣ n :=
begin
  rw is_prime_pow_nat_iff,
  split,
  { rintro ⟨p, k, hp, hk, rfl⟩,
    refine ⟨p, ⟨hp, dvd_pow_self _ hk.ne'⟩, _⟩,
    rintro q ⟨hq, hq'⟩,
    exact (nat.prime_dvd_prime_iff_eq hq hp).1 (hq.dvd_of_dvd_pow hq') },
  rintro ⟨p, ⟨hp, hn⟩, hq⟩,
  rcases eq_or_ne n 0 with rfl | hn₀,
  { cases (hq 2 ⟨nat.prime_two, dvd_zero 2⟩).trans (hq 3 ⟨nat.prime_three, dvd_zero 3⟩).symm },
  refine ⟨p, n.factorization p, hp, hp.factorization_pos_of_dvd hn₀ hn, _⟩,
  simp only [and_imp] at hq,
  apply nat.dvd_antisymm (nat.ord_proj_dvd _ _),
  -- We need to show n ∣ p ^ n.factorization p
  apply nat.dvd_of_factors_subperm hn₀,
  rw [hp.factors_pow, list.subperm_ext_iff],
  intros q hq',
  rw nat.mem_factors hn₀ at hq',
  cases hq _ hq'.1 hq'.2,
  simp,
end

lemma is_prime_pow_pow_iff {n k : ℕ} (hk : k ≠ 0) :
  is_prime_pow (n ^ k) ↔ is_prime_pow n :=
begin
  simp only [is_prime_pow_iff_unique_prime_dvd],
  apply exists_unique_congr,
  simp only [and.congr_right_iff],
  intros p hp,
  exact ⟨hp.dvd_of_dvd_pow, λ t, t.trans (dvd_pow_self _ hk)⟩,
end

lemma nat.coprime.is_prime_pow_dvd_mul {n a b : ℕ} (hab : nat.coprime a b) (hn : is_prime_pow n) :
  n ∣ a * b ↔ n ∣ a ∨ n ∣ b :=
begin
  rcases eq_or_ne a 0 with rfl | ha,
  { simp only [nat.coprime_zero_left] at hab,
    simp [hab, finset.filter_singleton, not_is_prime_pow_one] },
  rcases eq_or_ne b 0 with rfl | hb,
  { simp only [nat.coprime_zero_right] at hab,
    simp [hab, finset.filter_singleton, not_is_prime_pow_one] },
  refine ⟨_, λ h, or.elim h (λ i, i.trans (dvd_mul_right _ _)) (λ i, i.trans (dvd_mul_left _ _))⟩,
  obtain ⟨p, k, hp, hk, rfl⟩ := (is_prime_pow_nat_iff _).1 hn,
  simp only [hp.pow_dvd_iff_le_factorization (mul_ne_zero ha hb),
    nat.factorization_mul ha hb, hp.pow_dvd_iff_le_factorization ha,
    hp.pow_dvd_iff_le_factorization hb, pi.add_apply, finsupp.coe_add],
  have : a.factorization p = 0 ∨ b.factorization p = 0,
  { rw [←finsupp.not_mem_support_iff, ←finsupp.not_mem_support_iff, ←not_and_distrib,
      ←finset.mem_inter],
    exact λ t, (nat.factorization_disjoint_of_coprime hab).le_bot t },
  cases this;
  simp [this, imp_or_distrib],
end

lemma nat.mul_divisors_filter_prime_pow {a b : ℕ} (hab : a.coprime b) :
  (a * b).divisors.filter is_prime_pow = (a.divisors ∪ b.divisors).filter is_prime_pow :=
begin
  rcases eq_or_ne a 0 with rfl | ha,
  { simp only [nat.coprime_zero_left] at hab,
    simp [hab, finset.filter_singleton, not_is_prime_pow_one] },
  rcases eq_or_ne b 0 with rfl | hb,
  { simp only [nat.coprime_zero_right] at hab,
    simp [hab, finset.filter_singleton, not_is_prime_pow_one] },
  ext n,
  simp only [ha, hb, finset.mem_union, finset.mem_filter, nat.mul_eq_zero, and_true, ne.def,
    and.congr_left_iff, not_false_iff, nat.mem_divisors, or_self],
  apply hab.is_prime_pow_dvd_mul,
end


import utilities

open list
open multiset
open set
open nat

set_option trace.simplify.rewrite true

variable {α : Type*}
variable r: α → α → Prop
variable x: α
variable xs: list α

/-
# Insertion sort

The two functions insort and isort from __Functional Algorithms, Verified!__ are defined in Lean
as ordered_insert and insertion_sort respectively and are reused.

## Functional Correctness
 -/

lemma mset_insort [decidable_rel r] : (ordered_insert r x xs: multiset α) = {x} + ↑xs :=
begin
  induction' xs,
  { refl },
  { simp,
    split_ifs,
      refl,
      simp [← multiset.cons_coe, ih] }
end

lemma mset_isort [decidable_rel r] : (insertion_sort r xs: multiset α) = ↑xs :=
begin
  induction' xs,
  { refl },
  { simp [mset_insort, ih],
    refl }
end

lemma set_insort [decidable_rel r] : (ordered_insert r x xs).to_set  = {x} ∪ xs.to_set  :=
begin
  simp [set.insert_def, ← set_mset_mset, mset_insort, multiset.to_set],
end

lemma sorted_insort [decidable_rel r] [is_linear_order α r] : sorted' r (ordered_insert r x xs) = sorted' r xs :=
begin
  -- By using fixing the trans and total_of functions work without writing the is_total and is_trans instances explicitly.
  induction' xs fixing *,
  { simp [sorted'],
    intros,
    exact false.elim H },
  { simp only [ordered_insert],
    split_ifs,
    { simp [sorted', list.to_set],
      intros h1 h2,
      apply and.intro h,
      intros y h3,
      have h4: y ∈ xs.to_set → r hd y, from h1 y,
      have h5: r hd y, from h4 h3,
      exact trans h h5 },
    { simp [sorted', list.to_set, ih, set_insort],
      intros h1 h2,
      exact or.resolve_right (total_of r hd x) h } }
end

lemma sorted_isort [decidable_rel r] [is_linear_order α r]: sorted' r (insertion_sort r xs) :=
begin
  induction' xs,
  repeat { simp [sorted_insort, *] }
end

/-
## Time Complexity
We count the number of function calls.
 -/

def T_insort [decidable_rel r] : α → list α → nat
| x [] := 1
| x (y::ys) := if  r x y  then 0 else T_insort x ys + 1

def T_isort [decidable_rel r] : list α → nat
| [] := 1
| (x::xs) := T_isort xs + T_insort r x (insertion_sort r xs) + 1

lemma T_insort_length [decidable_rel r]: T_insort r x xs <= xs.length + 1 :=
begin
  induction' xs,
  repeat { simp [T_insort] },
  split_ifs,
  repeat { simp *},
end

lemma length_insort [decidable_rel r] : (ordered_insert r x xs).length = xs.length + 1 :=
begin
  induction' xs,
  repeat { simp [ordered_insert] },
  split_ifs,
  repeat { simp *},
end

lemma length_isort [decidable_rel r] : (insertion_sort r xs).length = xs.length :=
begin
  induction' xs,
  repeat { simp [length_insort, *] }
end

/-
Lemma 2.1 from __Functional Algorithms, Verified!__
-/
lemma T_isort_length [decidable_rel r]: T_isort r xs <= (xs.length + 1) ^ 2 :=
begin
  induction' xs fixing *,
  repeat { simp [T_isort, T_insort_length, length_isort ]},
  show T_isort r xs + T_insort r hd (insertion_sort r xs) + 1 ≤ (xs.length + 1 + 1) ^ 2, by calc
  T_isort r xs + T_insort r hd (insertion_sort r xs) + 1 ≤ (xs.length + 1) ^ 2 + T_insort r hd (insertion_sort r xs) + 1 : by simp [ih]
  ... ≤ (xs.length + 1) ^ 2 + ((insertion_sort r xs).length + 1) + 1 : by simp [T_insort_length]
  ... = (xs.length + 1) ^ 2 + (xs.length + 1) + 1 : by simp [length_isort]
  ... = xs.length ^2 + 2 * xs.length + xs.length + 3 : by ring
  ... ≤ xs.length ^2 + 2 * xs.length + xs.length + xs.length + 3 : by simp
  ... ≤ xs.length ^2 + 2 * xs.length + xs.length + xs.length + 4 : by simp
  ... = (xs.length + 1 + 1) ^ 2 : by ring,
end


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Premetric spaces.

Author: Sébastien Gouëzel

Metric spaces are often defined as quotients of spaces endowed with a "distance"
function satisfying the triangular inequality, but for which `dist x y = 0` does
not imply x = y. We call such a space a premetric space.
`dist x y = 0` defines an equivalence relation, and the quotient
is canonically a metric space.
-/

import topology.metric_space.basic tactic.linarith
noncomputable theory

universes u v
variables {α : Type u}

class premetric_space (α : Type u) extends has_dist α : Type u :=
(dist_self : ∀ x : α, dist x x = 0)
(dist_comm : ∀ x y : α, dist x y = dist y x)
(dist_triangle : ∀ x y z : α, dist x z ≤ dist x y + dist y z)

namespace premetric
section

protected lemma dist_nonneg {α : Type u} [premetric_space α] {x y : α} : 0 ≤ dist x y :=
begin
  have := calc
    0 = dist x x : (premetric_space.dist_self _).symm
    ... ≤ dist x y + dist y x : premetric_space.dist_triangle _ _ _
    ... = dist x y + dist x y : by simp [premetric_space.dist_comm],
  by linarith
end

/-- The canonical equivalence relation on a premetric space. -/
def dist_setoid (α : Type u) [premetric_space α] : setoid α :=
setoid.mk (λx y, dist x y = 0)
begin
  unfold equivalence,
  repeat { split },
  { exact premetric_space.dist_self },
  { assume x y h, rwa premetric_space.dist_comm },
  { assume x y z hxy hyz,
    refine le_antisymm _ premetric.dist_nonneg,
    calc dist x z ≤ dist x y + dist y z : premetric_space.dist_triangle _ _ _
         ... = 0 + 0 : by rw [hxy, hyz]
         ... = 0 : by simp }
end

local attribute [instance] dist_setoid

/-- The canonical quotient of a premetric space, identifying points at distance 0. -/
@[reducible] definition metric_quot (α : Type u) [premetric_space α] : Type* :=
quotient (premetric.dist_setoid α)

instance has_dist_metric_quot {α : Type u} [premetric_space α] : has_dist (metric_quot α) :=
{ dist := quotient.lift₂ (λp q : α, dist p q)
begin
  assume x y x' y' hxx' hyy',
  have Hxx' : dist x x' = 0 := hxx',
  have Hyy' : dist y y' = 0 := hyy',
  have A : dist x y ≤ dist x' y' := calc
    dist x y ≤ dist x x' + dist x' y : premetric_space.dist_triangle _ _ _
    ... = dist x' y : by simp [Hxx']
    ... ≤ dist x' y' + dist y' y : premetric_space.dist_triangle _ _ _
    ... = dist x' y' : by simp [premetric_space.dist_comm, Hyy'],
  have B : dist x' y' ≤ dist x y := calc
    dist x' y' ≤ dist x' x + dist x y' : premetric_space.dist_triangle _ _ _
    ... = dist x y' : by simp [premetric_space.dist_comm, Hxx']
    ... ≤ dist x y + dist y y' : premetric_space.dist_triangle _ _ _
    ... = dist x y : by simp [Hyy'],
  exact le_antisymm A B
end }

lemma metric_quot_dist_eq {α : Type u} [premetric_space α] (p q : α) : dist ⟦p⟧ ⟦q⟧ = dist p q := rfl

instance metric_space_quot {α : Type u} [premetric_space α] : metric_space (metric_quot α) :=
{ dist_self := begin
    refine quotient.ind (λy, _),
    exact premetric_space.dist_self _
  end,
  eq_of_dist_eq_zero :=
    λxc yc, quotient.induction_on₂ xc yc (λx y H, quotient.sound H),
  dist_comm :=
    λxc yc, quotient.induction_on₂ xc yc (λx y, premetric_space.dist_comm _ _),
  dist_triangle :=
    λxc yc zc, quotient.induction_on₃ xc yc zc (λx y z, premetric_space.dist_triangle _ _ _) }

end --section
end premetric --namespace


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Amelia Livingston, Yury Kudryashov,
Neil Strickland, Aaron Anderson
-/

import algebra.group_with_zero.basic

/-!
# Divisibility

This file defines the basics of the divisibility relation in the context of `(comm_)` `monoid`s
`(_with_zero)`.

## Main definitions

 * `monoid.has_dvd`

## Implementation notes

The divisibility relation is defined for all monoids, and as such, depends on the order of
  multiplication if the monoid is not commutative. There are two possible conventions for
  divisibility in the noncommutative context, and this relation follows the convention for ordinals,
  so `a | b` is defined as `∃ c, b = a * c`.

## Tags

divisibility, divides
-/

variables {α : Type*}

section semigroup

variables [semigroup α] {a b c : α}

/-- There are two possible conventions for divisibility, which coincide in a `comm_monoid`.
    This matches the convention for ordinals. -/
@[priority 100]
instance semigroup_has_dvd : has_dvd α :=
has_dvd.mk (λ a b, ∃ c, b = a * c)

-- TODO: this used to not have `c` explicit, but that seems to be important
--       for use with tactics, similar to `exists.intro`
theorem dvd.intro (c : α) (h : a * c = b) : a ∣ b :=
exists.intro c h^.symm

alias dvd.intro ← dvd_of_mul_right_eq

theorem exists_eq_mul_right_of_dvd (h : a ∣ b) : ∃ c, b = a * c := h

theorem dvd.elim {P : Prop} {a b : α} (H₁ : a ∣ b) (H₂ : ∀ c, b = a * c → P) : P :=
exists.elim H₁ H₂

local attribute [simp] mul_assoc mul_comm mul_left_comm

@[trans] theorem dvd_trans (h₁ : a ∣ b) (h₂ : b ∣ c) : a ∣ c :=
match h₁, h₂ with
| ⟨d, (h₃ : b = a * d)⟩, ⟨e, (h₄ : c = b * e)⟩ :=
  ⟨d * e, show c = a * (d * e), by simp [h₃, h₄]⟩
end

alias dvd_trans ← has_dvd.dvd.trans

@[simp] theorem dvd_mul_right (a b : α) : a ∣ a * b := dvd.intro b rfl

theorem dvd_mul_of_dvd_left (h : a ∣ b) (c : α) : a ∣ b * c :=
h.trans (dvd_mul_right b c)

alias dvd_mul_of_dvd_left ← has_dvd.dvd.mul_right

theorem dvd_of_mul_right_dvd (h : a * b ∣ c) : a ∣ c :=
(dvd_mul_right a b).trans h

section map_dvd

variables {M N : Type*} [monoid M] [monoid N]

lemma map_dvd {F : Type*} [mul_hom_class F M N] (f : F) {a b} : a ∣ b → f a ∣ f b
| ⟨c, h⟩ := ⟨f c, h.symm ▸ map_mul f a c⟩

lemma mul_hom.map_dvd (f : mul_hom M N) {a b} : a ∣ b → f a ∣ f b := map_dvd f

lemma monoid_hom.map_dvd (f : M →* N) {a b} : a ∣ b → f a ∣ f b := map_dvd f

end map_dvd

end semigroup

section monoid

variables [monoid α]

@[refl, simp] theorem dvd_refl (a : α) : a ∣ a :=
dvd.intro 1 (mul_one _)

lemma dvd_rfl {a : α} : a ∣ a :=
dvd_refl a

theorem one_dvd (a : α) : 1 ∣ a := dvd.intro a (one_mul _)

end monoid

section comm_semigroup

variables [comm_semigroup α] {a b c : α}

theorem dvd.intro_left (c : α) (h : c * a = b) : a ∣ b :=
dvd.intro _ (begin rewrite mul_comm at h, apply h end)

alias dvd.intro_left ← dvd_of_mul_left_eq

theorem exists_eq_mul_left_of_dvd (h : a ∣ b) : ∃ c, b = c * a :=
dvd.elim h (assume c, assume H1 : b = a * c, exists.intro c (eq.trans H1 (mul_comm a c)))

lemma dvd_iff_exists_eq_mul_left : a ∣ b ↔ ∃ c, b = c * a :=
⟨exists_eq_mul_left_of_dvd, by { rintro ⟨c, rfl⟩, exact ⟨c, mul_comm _ _⟩, }⟩

theorem dvd.elim_left {P : Prop} (h₁ : a ∣ b) (h₂ : ∀ c, b = c * a → P) : P :=
exists.elim (exists_eq_mul_left_of_dvd h₁) (assume c, assume h₃ : b = c * a, h₂ c h₃)

@[simp] theorem dvd_mul_left (a b : α) : a ∣ b * a := dvd.intro b (mul_comm a b)

theorem dvd_mul_of_dvd_right (h : a ∣ b) (c : α) : a ∣ c * b :=
begin rw mul_comm, exact h.mul_right _ end

alias dvd_mul_of_dvd_right ← has_dvd.dvd.mul_left

local attribute [simp] mul_assoc mul_comm mul_left_comm

theorem mul_dvd_mul : ∀ {a b c d : α}, a ∣ b → c ∣ d → a * c ∣ b * d
| a ._ c ._ ⟨e, rfl⟩ ⟨f, rfl⟩ := ⟨e * f, by simp⟩

theorem dvd_of_mul_left_dvd (h : a * b ∣ c) : b ∣ c :=
dvd.elim h (λ d ceq, dvd.intro (a * d) (by simp [ceq]))

end comm_semigroup

section comm_monoid

variables [comm_monoid α] {a b : α}

theorem mul_dvd_mul_left (a : α) {b c : α} (h : b ∣ c) : a * b ∣ a * c :=
mul_dvd_mul (dvd_refl a) h

theorem mul_dvd_mul_right (h : a ∣ b) (c : α) : a * c ∣ b * c :=
mul_dvd_mul h (dvd_refl c)

end comm_monoid

section semigroup_with_zero

variables [semigroup_with_zero α] {a : α}

theorem eq_zero_of_zero_dvd (h : 0 ∣ a) : a = 0 :=
dvd.elim h (assume c, assume H' : a = 0 * c, eq.trans H' (zero_mul c))

/-- Given an element `a` of a commutative semigroup with zero, there exists another element whose
    product with zero equals `a` iff `a` equals zero. -/
@[simp] lemma zero_dvd_iff : 0 ∣ a ↔ a = 0 :=
⟨eq_zero_of_zero_dvd, λ h, by { rw h, use 0, simp, }⟩

@[simp] theorem dvd_zero (a : α) : a ∣ 0 := dvd.intro 0 (by simp)

end semigroup_with_zero

/-- Given two elements `b`, `c` of a `cancel_monoid_with_zero` and a nonzero element `a`,
 `a*b` divides `a*c` iff `b` divides `c`. -/
theorem mul_dvd_mul_iff_left [cancel_monoid_with_zero α] {a b c : α}
  (ha : a ≠ 0) : a * b ∣ a * c ↔ b ∣ c :=
exists_congr $ λ d, by rw [mul_assoc, mul_right_inj' ha]

/-- Given two elements `a`, `b` of a commutative `cancel_monoid_with_zero` and a nonzero
  element `c`, `a*c` divides `b*c` iff `a` divides `b`. -/
theorem mul_dvd_mul_iff_right [cancel_comm_monoid_with_zero α] {a b c : α} (hc : c ≠ 0) :
  a * c ∣ b * c ↔ a ∣ b :=
exists_congr $ λ d, by rw [mul_right_comm, mul_left_inj' hc]

/-!
### Units in various monoids
-/

namespace units

section monoid
variables [monoid α] {a b : α} {u : αˣ}

/-- Elements of the unit group of a monoid represented as elements of the monoid
    divide any element of the monoid. -/
lemma coe_dvd : ↑u ∣ a := ⟨↑u⁻¹ * a, by simp⟩

/-- In a monoid, an element `a` divides an element `b` iff `a` divides all
    associates of `b`. -/
lemma dvd_mul_right : a ∣ b * u ↔ a ∣ b :=
iff.intro
  (assume ⟨c, eq⟩, ⟨c * ↑u⁻¹, by rw [← mul_assoc, ← eq, units.mul_inv_cancel_right]⟩)
  (assume ⟨c, eq⟩, eq.symm ▸ (dvd_mul_right _ _).mul_right _)

/-- In a monoid, an element `a` divides an element `b` iff all associates of `a` divide `b`. -/
lemma mul_right_dvd : a * u ∣ b ↔ a ∣ b :=
iff.intro
  (λ ⟨c, eq⟩, ⟨↑u * c, eq.trans (mul_assoc _ _ _)⟩)
  (λ h, dvd_trans (dvd.intro ↑u⁻¹ (by rw [mul_assoc, u.mul_inv, mul_one])) h)

end monoid

section comm_monoid
variables [comm_monoid α] {a b : α} {u : αˣ}

/-- In a commutative monoid, an element `a` divides an element `b` iff `a` divides all left
    associates of `b`. -/
lemma dvd_mul_left : a ∣ u * b ↔ a ∣ b := by { rw mul_comm, apply dvd_mul_right }

/-- In a commutative monoid, an element `a` divides an element `b` iff all
  left associates of `a` divide `b`.-/
lemma mul_left_dvd : ↑u * a ∣ b ↔ a ∣ b :=
by { rw mul_comm, apply mul_right_dvd }

end comm_monoid

end units

namespace is_unit

section monoid

variables [monoid α] {a b u : α} (hu : is_unit u)
include hu

/-- Units of a monoid divide any element of the monoid. -/
@[simp] lemma dvd : u ∣ a := by { rcases hu with ⟨u, rfl⟩, apply units.coe_dvd, }

@[simp] lemma dvd_mul_right : a ∣ b * u ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.dvd_mul_right, }

/-- In a monoid, an element a divides an element b iff all associates of `a` divide `b`.-/
@[simp] lemma mul_right_dvd : a * u ∣ b ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.mul_right_dvd, }

end monoid

section comm_monoid
variables [comm_monoid α] (a b u : α) (hu : is_unit u)
include hu

/-- In a commutative monoid, an element `a` divides an element `b` iff `a` divides all left
    associates of `b`. -/
@[simp] lemma dvd_mul_left : a ∣ u * b ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.dvd_mul_left, }

/-- In a commutative monoid, an element `a` divides an element `b` iff all
  left associates of `a` divide `b`.-/
@[simp] lemma mul_left_dvd : u * a ∣ b ↔ a ∣ b :=
by { rcases hu with ⟨u, rfl⟩, apply units.mul_left_dvd, }

end comm_monoid

end is_unit

section comm_monoid
variables [comm_monoid α]

theorem is_unit_iff_dvd_one {x : α} : is_unit x ↔ x ∣ 1 :=
⟨by rintro ⟨u, rfl⟩; exact ⟨_, u.mul_inv.symm⟩,
 λ ⟨y, h⟩, ⟨⟨x, y, h.symm, by rw [h, mul_comm]⟩, rfl⟩⟩

theorem is_unit_iff_forall_dvd {x : α} :
  is_unit x ↔ ∀ y, x ∣ y :=
is_unit_iff_dvd_one.trans ⟨λ h y, h.trans (one_dvd _), λ h, h _⟩

theorem is_unit_of_dvd_unit {x y : α}
  (xy : x ∣ y) (hu : is_unit y) : is_unit x :=
is_unit_iff_dvd_one.2 $ xy.trans $ is_unit_iff_dvd_one.1 hu

lemma is_unit_of_dvd_one : ∀a ∣ 1, is_unit (a:α)
| a ⟨b, eq⟩ := ⟨units.mk_of_mul_eq_one a b eq.symm, rfl⟩

lemma not_is_unit_of_not_is_unit_dvd {a b : α} (ha : ¬is_unit a) (hb : a ∣ b) :
  ¬ is_unit b :=
mt (is_unit_of_dvd_unit hb) ha

end comm_monoid

section comm_monoid_with_zero

variable [comm_monoid_with_zero α]

/-- `dvd_not_unit a b` expresses that `a` divides `b` "strictly", i.e. that `b` divided by `a`
is not a unit. -/
def dvd_not_unit (a b : α) : Prop := a ≠ 0 ∧ ∃ x, ¬is_unit x ∧ b = a * x

lemma dvd_not_unit_of_dvd_of_not_dvd {a b : α} (hd : a ∣ b) (hnd : ¬ b ∣ a) :
  dvd_not_unit a b :=
begin
  split,
  { rintro rfl, exact hnd (dvd_zero _) },
  { rcases hd with ⟨c, rfl⟩,
    refine ⟨c, _, rfl⟩,
    rintro ⟨u, rfl⟩,
    simpa using hnd }
end

end comm_monoid_with_zero

lemma dvd_and_not_dvd_iff [cancel_comm_monoid_with_zero α] {x y : α} :
  x ∣ y ∧ ¬y ∣ x ↔ dvd_not_unit x y :=
⟨λ ⟨⟨d, hd⟩, hyx⟩, ⟨λ hx0, by simpa [hx0] using hyx, ⟨d,
    mt is_unit_iff_dvd_one.1 (λ ⟨e, he⟩, hyx ⟨e, by rw [hd, mul_assoc, ← he, mul_one]⟩), hd⟩⟩,
  λ ⟨hx0, d, hdu, hdx⟩, ⟨⟨d, hdx⟩, λ ⟨e, he⟩, hdu (is_unit_of_dvd_one _
    ⟨e, mul_left_cancel₀ hx0 $ by conv {to_lhs, rw [he, hdx]};simp [mul_assoc]⟩)⟩⟩

section monoid_with_zero

variable [monoid_with_zero α]

theorem ne_zero_of_dvd_ne_zero {p q : α} (h₁ : q ≠ 0)
  (h₂ : p ∣ q) : p ≠ 0 :=
begin
  rcases h₂ with ⟨u, rfl⟩,
  exact left_ne_zero_of_mul h₁,
end

end monoid_with_zero


import game.world8.level6 -- hide
namespace mynat -- hide

/-

# Advanced Addition World

## Level 7: `add_right_cancel_iff`

It's sometimes convenient to have the "if and only if" version
of theorems like `add_right_cancel`. Remember that you can use `split`
to split an `↔` goal into the `→` goal and the `←` goal.

## Pro tip:

`exact add_right_cancel _ _ _` means "let Lean figure out the missing inputs"
-/

/- Theorem
For all naturals $a$, $b$ and $t$,
$$ a + t = b + t\iff a=b. $$
-/
theorem add_right_cancel_iff (t a b : mynat) :  a + t = b + t ↔ a = b :=
begin [nat_num_game]
  split,
  { exact add_right_cancel _ _ _}, -- done that way already,
  { intro H, -- H : a = b,
    rw H,
    refl,
  }

end

end mynat -- hide


import analysis.normed_space.finite_dimension
import analysis.convolution
import measure_theory.function.jacobian
import measure_theory.integral.bochner
import measure_theory.measure.lebesgue

open set filter
open_locale topological_space filter ennreal
noncomputable theory

/- TEXT:
.. index:: measure theory

.. _measure_theory:

Measure Theory
--------------

The general context for integration in mathlib is measure theory. Even the elementary
integrals of the previous section are in fact Bochner integrals. Bochner integration is
a generalization of Lebesgue integration where the target space can be any Banach space,
not necessarily finite dimensional.

The first component in the development of measure theory
is the notion of a :math:`\sigma`-algebra of sets, which are called the
*measurable* sets.
The type class ``measurable_space`` serves to equip a type with such a structure.
The sets ``empty`` and ``univ`` are measurable,
the complement of a measurable set is measurable,
and a countable union or intersection of measurable sets is measurable.
Note that these axioms are redundant; if you ``#print measurable_space``,
you will see the ones that mathlib uses.
As the examples below show, countability assumptions can be expressed using the
``encodable`` type class.
BOTH: -/
-- QUOTE:
variables {α : Type*} [measurable_space α]

-- EXAMPLES:
example : measurable_set (∅ : set α) := measurable_set.empty

example : measurable_set (univ : set α) := measurable_set.univ

example {s : set α} (hs : measurable_set s) : measurable_set sᶜ :=
hs.compl

example : encodable ℕ :=
by apply_instance

example (n : ℕ) : encodable (fin n) :=
by apply_instance

-- BOTH:
variables {ι : Type*} [encodable ι]

-- EXAMPLES:
example {f : ι → set α} (h : ∀ b, measurable_set (f b)) :
  measurable_set (⋃ b, f b) :=
measurable_set.Union h

example {f : ι → set α} (h : ∀ b, measurable_set (f b)) :
  measurable_set (⋂ b, f b) :=
measurable_set.Inter h
-- QUOTE.

/- TEXT:
Once a type is measurable, we can measure it. On paper, a measure on a set
(or type) equipped with a
:math:`\sigma`-algebra is a function from the measurable sets to
the extended non-negative reals that is
additive on countable disjoint unions.
In mathlib, we don't want to carry around measurability assumptions
every time we write an application of the measure to a set.
So we extend the measure to any set ``s``
as the infimum of measures of measurable sets containing ``s``.
Of course, many lemmas still require
measurability assumptions, but not all.
BOTH: -/
-- QUOTE:
open measure_theory

variables {μ : measure α}

-- EXAMPLES:
example (s : set α) : μ s = ⨅ t (st : s ⊆ t) (ht : measurable_set t), μ t :=
measure_eq_infi s

example  (s : ι → set α) : μ (⋃ i, s i) ≤ ∑' i, μ (s i) :=
measure_Union_le s

example {f : ℕ → set α}
    (hmeas : ∀ i, measurable_set (f i)) (hdis : pairwise (disjoint on f)) :
  μ (⋃ i, f i) = ∑' i, μ (f i) :=
μ.m_Union hmeas hdis
-- QUOTE.

/- TEXT:
Once a type has a measure associated with it, we say that a property ``P``
holds *almost everywhere* if the set of elements where the property fails
has measure 0.
The collection of properties that hold almost everywhere form a filter,
but mathlib introduces special notation for saying that a property holds
almost everywhere.
EXAMPLES: -/
-- QUOTE:
example {P : α → Prop} : (∀ᵐ x ∂μ, P x) ↔ ∀ᶠ x in μ.ae, P x :=
iff.rfl
-- QUOTE.


/-
Copyright (c) 2022 Michael Stoll. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Michael Stoll
-/
import number_theory.legendre_symbol.jacobi_symbol

/-!
# A `norm_num` extension for Jacobi and Legendre symbols

We extend the `tactic.interactive.norm_num` tactic so that it can be used to provably compute
the value of the Jacobi symbol `J(a | b)` or the Legendre symbol `legendre_sym p a` when
the arguments are numerals.

## Implementation notes

We use the Law of Quadratic Reciprocity for the Jacobi symbol to compute the value of `J(a | b)`
efficiently, roughly comparable in effort with the euclidean algorithm for the computation
of the gcd of `a` and `b`. More precisely, the computation is done in the following steps.

* Use `J(a | 0) = 1` (an artifact of the definition) and `J(a | 1) = 1` to deal
  with corner cases.

* Use `J(a | b) = J(a % b | b)` to reduce to the case that `a` is a natural number.
  We define a version of the Jacobi symbol restricted to natural numbers for use in
  the following steps; see `norm_num.jacobi_sym_nat`. (But we'll continue to write `J(a | b)`
  in this description.)

* Remove powers of two from `b`. This is done via `J(2a | 2b) = 0` and
  `J(2a+1 | 2b) = J(2a+1 | b)` (another artifact of the definition).

* Now `0 ≤ a < b` and `b` is odd. If `b = 1`, then the value is `1`.
  If `a = 0` (and `b > 1`), then the value is `0`. Otherwise, we remove powers of two from `a`
  via `J(4a | b) = J(a | b)` and `J(2a | b) = ±J(a | b)`, where the sign is determined
  by the residue class of `b` mod 8, to reduce to `a` odd.

* Once `a` is odd, we use Quadratic Reciprocity (QR) in the form
  `J(a | b) = ±J(b % a | a)`, where the sign is determined by the residue classes
  of `a` and `b` mod 4. We are then back in the previous case.

We provide customized versions of these results for the various reduction steps,
where we encode the residue classes mod 2, mod 4, or mod 8 by using terms like
`bit1 (bit0 a)`. In this way, the only divisions we have to compute and prove
are the ones occurring in the use of QR above.
-/

section lemmas

namespace norm_num

/-- The Jacobi symbol restricted to natural numbers in both arguments. -/
def jacobi_sym_nat (a b : ℕ) : ℤ  := jacobi_sym a b

/-!
### API Lemmas

We repeat part of the API for `jacobi_sym` with `norm_num.jacobi_sym_nat` and without implicit
arguments, in a form that is suitable for constructing proofs in `norm_num`.
-/

/-- Base cases: `b = 0`, `b = 1`, `a = 0`, `a = 1`. -/
lemma jacobi_sym_nat.zero_right (a : ℕ) : jacobi_sym_nat a 0 = 1 :=
by rwa [jacobi_sym_nat, jacobi_sym.zero_right]

lemma jacobi_sym_nat.one_right (a : ℕ) : jacobi_sym_nat a 1 = 1 :=
by rwa [jacobi_sym_nat, jacobi_sym.one_right]

lemma jacobi_sym_nat.zero_left_even (b : ℕ) (hb : b ≠ 0) : jacobi_sym_nat 0 (bit0 b) = 0 :=
by rw [jacobi_sym_nat, nat.cast_zero, jacobi_sym.zero_left (nat.one_lt_bit0 hb)]

lemma jacobi_sym_nat.zero_left_odd (b : ℕ) (hb : b ≠ 0) : jacobi_sym_nat 0 (bit1 b) = 0 :=
by rw [jacobi_sym_nat, nat.cast_zero, jacobi_sym.zero_left (nat.one_lt_bit1 hb)]

lemma jacobi_sym_nat.one_left_even (b : ℕ) : jacobi_sym_nat 1 (bit0 b) = 1 :=
by rw [jacobi_sym_nat, nat.cast_one, jacobi_sym.one_left]

lemma jacobi_sym_nat.one_left_odd (b : ℕ) : jacobi_sym_nat 1 (bit1 b) = 1 :=
by rw [jacobi_sym_nat, nat.cast_one, jacobi_sym.one_left]

/-- Turn a Legendre symbol into a Jacobi symbol. -/
lemma legendre_sym.to_jacobi_sym (p : ℕ) (pp : fact (p.prime)) (a r : ℤ) (hr : jacobi_sym a p = r) :
  legendre_sym p a = r :=
by rwa [@legendre_sym.to_jacobi_sym p pp a]

/-- The value depends only on the residue class of `a` mod `b`. -/
lemma jacobi_sym.mod_left (a : ℤ) (b ab' : ℕ) (ab r b' : ℤ) (hb' : (b : ℤ) = b')
  (hab : a % b' = ab) (h : (ab' : ℤ) = ab) (hr : jacobi_sym_nat ab' b = r) :
  jacobi_sym a b = r :=
by rw [← hr, jacobi_sym_nat, jacobi_sym.mod_left, hb', hab, ← h]

lemma jacobi_sym_nat.mod_left (a b ab : ℕ) (r : ℤ) (hab : a % b = ab)
  (hr : jacobi_sym_nat ab b = r) :
  jacobi_sym_nat a b = r :=
by { rw [← hr, jacobi_sym_nat, jacobi_sym_nat, _root_.jacobi_sym.mod_left a b, ← hab], refl, }

/-- The symbol vanishes when both entries are even (and `b ≠ 0`). -/
lemma jacobi_sym_nat.even_even (a b : ℕ) (hb₀ : b ≠ 0) :
  jacobi_sym_nat (bit0 a) (bit0 b) = 0 :=
begin
  refine jacobi_sym.eq_zero_iff.mpr ⟨nat.bit0_ne_zero hb₀, λ hf, _⟩,
  have h : 2 ∣ (bit0 a).gcd (bit0 b) := nat.dvd_gcd two_dvd_bit0 two_dvd_bit0,
  change 2 ∣ (bit0 a : ℤ).gcd (bit0 b) at h,
  rw [← nat.cast_bit0, ← nat.cast_bit0, hf, ← even_iff_two_dvd] at h,
  exact nat.not_even_one h,
end

/-- When `a` is odd and `b` is even, we can replace `b` by `b / 2`. -/
lemma jacobi_sym_nat.odd_even (a b : ℕ) (r : ℤ) (hr : jacobi_sym_nat (bit1 a) b = r) :
  jacobi_sym_nat (bit1 a) (bit0 b) = r :=
begin
  have ha : legendre_sym 2 (bit1 a) = 1 :=
  by simp only [legendre_sym, quadratic_char_apply, quadratic_char_fun_one, int.cast_bit1,
                char_two.bit1_eq_one, pi.one_apply],
  cases eq_or_ne b 0 with hb hb,
  { rw [← hr, hb, jacobi_sym_nat.zero_right], },
  { haveI : ne_zero b := ⟨hb⟩, -- for `jacobi_sym.mul_right`
    rwa [bit0_eq_two_mul b, jacobi_sym_nat, jacobi_sym.mul_right,
         ← _root_.legendre_sym.to_jacobi_sym, nat.cast_bit1, ha, one_mul], }
end

/-- If `a` is divisible by `4` and `b` is odd, then we can remove the factor `4` from `a`. -/
lemma jacobi_sym_nat.double_even (a b : ℕ) (r : ℤ) (hr : jacobi_sym_nat a (bit1 b) = r) :
  jacobi_sym_nat (bit0 (bit0 a)) (bit1 b) = r :=
begin
  have : ((2 : ℕ) : ℤ).gcd ((bit1 b) : ℕ) = 1,
  { rw [int.coe_nat_gcd, nat.bit1_eq_succ_bit0, bit0_eq_two_mul b, nat.succ_eq_add_one,
        nat.gcd_mul_left_add_right, nat.gcd_one_right], },
  rwa [bit0_eq_two_mul a, bit0_eq_two_mul (2 * a), ← mul_assoc, ← pow_two, jacobi_sym_nat,
       nat.cast_mul, nat.cast_pow, jacobi_sym.mul_left, jacobi_sym.sq_one' this, one_mul],
end

/-- If `a` is even and `b` is odd, then we can remove a factor `2` from `a`,
but we may have to change the sign, depending on `b % 8`.
We give one version for each of the four odd residue classes mod `8`. -/
lemma jacobi_sym_nat.even_odd₁ (a b : ℕ) (r : ℤ)
  (hr : jacobi_sym_nat a (bit1 (bit0 (bit0 b))) = r) :
  jacobi_sym_nat (bit0 a) (bit1 (bit0 (bit0 b))) = r :=
begin
  have hb : (bit1 (bit0 (bit0 b))) % 8 = 1,
  { rw [nat.bit1_mod_bit0, nat.bit0_mod_bit0, nat.bit0_mod_two], },
  rw [jacobi_sym_nat, bit0_eq_two_mul a, nat.cast_mul, jacobi_sym.mul_left,
      nat.cast_two, jacobi_sym.at_two (odd_bit1 _), zmod.χ₈_nat_mod_eight, hb],
  norm_num,
  exact hr,
end

lemma jacobi_sym_nat.even_odd₇ (a b : ℕ) (r : ℤ)
  (hr : jacobi_sym_nat a (bit1 (bit1 (bit1 b))) = r) :
  jacobi_sym_nat (bit0 a) (bit1 (bit1 (bit1 b))) = r :=
begin
  have hb : (bit1 (bit1 (bit1 b))) % 8 = 7,
  { rw [nat.bit1_mod_bit0, nat.bit1_mod_bit0, nat.bit1_mod_two], },
  rw [jacobi_sym_nat, bit0_eq_two_mul a, nat.cast_mul, jacobi_sym.mul_left,
      nat.cast_two, jacobi_sym.at_two (odd_bit1 _), zmod.χ₈_nat_mod_eight, hb],
  norm_num,
  exact hr,
end

lemma jacobi_sym_nat.even_odd₃ (a b : ℕ) (r : ℤ)
  (hr : jacobi_sym_nat a (bit1 (bit1 (bit0 b))) = r) :
  jacobi_sym_nat (bit0 a) (bit1 (bit1 (bit0 b))) = -r :=
begin
  have hb : (bit1 (bit1 (bit0 b))) % 8 = 3,
  { rw [nat.bit1_mod_bit0, nat.bit1_mod_bit0, nat.bit0_mod_two], },
  rw [jacobi_sym_nat, bit0_eq_two_mul a, nat.cast_mul, jacobi_sym.mul_left,
      nat.cast_two, jacobi_sym.at_two (odd_bit1 _), zmod.χ₈_nat_mod_eight, hb],
  norm_num,
  exact hr,
end

lemma jacobi_sym_nat.even_odd₅ (a b : ℕ) (r : ℤ)
  (hr : jacobi_sym_nat a (bit1 (bit0 (bit1 b))) = r) :
  jacobi_sym_nat (bit0 a) (bit1 (bit0 (bit1 b))) = -r :=
begin
  have hb : (bit1 (bit0 (bit1 b))) % 8 = 5,
  { rw [nat.bit1_mod_bit0, nat.bit0_mod_bit0, nat.bit1_mod_two], },
  rw [jacobi_sym_nat, bit0_eq_two_mul a, nat.cast_mul, jacobi_sym.mul_left,
      nat.cast_two, jacobi_sym.at_two (odd_bit1 _), zmod.χ₈_nat_mod_eight, hb],
  norm_num,
  exact hr,
end

/-- Use quadratic reciproity to reduce to smaller `b`. -/
lemma jacobi_sym_nat.qr₁ (a b : ℕ) (r : ℤ) (hr : jacobi_sym_nat (bit1 b) (bit1 (bit0 a)) = r) :
  jacobi_sym_nat (bit1 (bit0 a)) (bit1 b) = r :=
begin
  have ha : (bit1 (bit0 a)) % 4 = 1,
  { rw [nat.bit1_mod_bit0, nat.bit0_mod_two], },
  have hb := nat.bit1_mod_two,
  rwa [jacobi_sym_nat, jacobi_sym.quadratic_reciprocity_one_mod_four ha (nat.odd_iff.mpr hb)],
end

lemma jacobi_sym_nat.qr₁_mod (a b ab : ℕ) (r : ℤ) (hab : (bit1 b) % (bit1 (bit0 a)) = ab)
  (hr : jacobi_sym_nat ab (bit1 (bit0 a)) = r) :
  jacobi_sym_nat (bit1 (bit0 a)) (bit1 b) = r :=
jacobi_sym_nat.qr₁ _ _ _ $ jacobi_sym_nat.mod_left _ _ ab r hab hr

lemma jacobi_sym_nat.qr₁' (a b : ℕ) (r : ℤ) (hr : jacobi_sym_nat (bit1 (bit0 b)) (bit1 a) = r) :
  jacobi_sym_nat (bit1 a) (bit1 (bit0 b)) = r :=
begin
  have hb : (bit1 (bit0 b)) % 4 = 1,
  { rw [nat.bit1_mod_bit0, nat.bit0_mod_two], },
  have ha := nat.bit1_mod_two,
  rwa [jacobi_sym_nat, ← jacobi_sym.quadratic_reciprocity_one_mod_four hb (nat.odd_iff.mpr ha)]
end

lemma jacobi_sym_nat.qr₁'_mod (a b ab : ℕ) (r : ℤ) (hab : (bit1 (bit0 b)) % (bit1 a) = ab)
  (hr : jacobi_sym_nat ab (bit1 a) = r) :
  jacobi_sym_nat (bit1 a) (bit1 (bit0 b)) = r :=
jacobi_sym_nat.qr₁' _ _ _ $ jacobi_sym_nat.mod_left _ _ ab r hab hr

lemma jacobi_sym_nat.qr₃ (a b : ℕ) (r : ℤ)
  (hr : jacobi_sym_nat (bit1 (bit1 b)) (bit1 (bit1 a)) = r) :
  jacobi_sym_nat (bit1 (bit1 a)) (bit1 (bit1 b)) = -r :=
begin
  have hb : (bit1 (bit1 b)) % 4 = 3,
  { rw [nat.bit1_mod_bit0, nat.bit1_mod_two], },
  have ha : (bit1 (bit1 a)) % 4 = 3,
  { rw [nat.bit1_mod_bit0, nat.bit1_mod_two], },
  rwa [jacobi_sym_nat, jacobi_sym.quadratic_reciprocity_three_mod_four ha hb, neg_inj]
end

lemma jacobi_sym_nat.qr₃_mod (a b ab : ℕ) (r : ℤ) (hab : (bit1 (bit1 b)) % (bit1 (bit1 a)) = ab)
  (hr : jacobi_sym_nat ab (bit1 (bit1 a)) = r) :
  jacobi_sym_nat (bit1 (bit1 a)) (bit1 (bit1 b)) = -r :=
jacobi_sym_nat.qr₃ _ _ _ $ jacobi_sym_nat.mod_left _ _ ab r hab hr

end norm_num

end lemmas

section evaluation

/-!
### Certified evaluation of the Jacobi symbol

The following functions recursively evaluate a Jacobi symbol and construct the
corresponding proof term.
-/

namespace norm_num
open tactic

/-- This evaluates `r := jacobi_sym_nat a b` recursively using quadratic reciprocity
and produces a proof term for the equality, assuming that `a < b` and `b` is odd. -/
meta def prove_jacobi_sym_odd : instance_cache → instance_cache → expr → expr →
   tactic (instance_cache × instance_cache × expr × expr)
| zc nc ea eb := do
  match match_numeral eb with
  | match_numeral_result.one :=  -- `b = 1`, result is `1`
    pure (zc, nc, `(1 : ℤ), `(jacobi_sym_nat.one_right).mk_app [ea])
  | match_numeral_result.bit1 eb₁ := do -- `b > 1` (recall that `b` is odd)
    match match_numeral ea with
    | match_numeral_result.zero := do -- `a = 0`, result is `0`
      b ← eb₁.to_nat,
      (nc, phb₀) ← prove_ne nc eb₁ `(0 : ℕ) b 0, -- proof of `b ≠ 0`
      pure (zc, nc, `(0 : ℤ), `(jacobi_sym_nat.zero_left_odd).mk_app [eb₁, phb₀])
    | match_numeral_result.one := do -- `a = 1`, result is `1`
      pure (zc, nc, `(1 : ℤ), `(jacobi_sym_nat.one_left_odd).mk_app [eb₁])
    | match_numeral_result.bit0 ea₁ := do -- `a` is even; check if divisible by `4`
      match match_numeral ea₁ with
      | match_numeral_result.bit0 ea₂ := do
        (zc, nc, er, p) ← prove_jacobi_sym_odd zc nc ea₂ eb, -- compute `jacobi_sym_nat (a / 4) b`
        pure (zc, nc, er, `(jacobi_sym_nat.double_even).mk_app [ea₂, eb₁, er, p])
      | _ := do -- reduce to `a / 2`; need to consider `b % 8`
        (zc, nc, er, p) ← prove_jacobi_sym_odd zc nc ea₁ eb, -- compute `jacobi_sym_nat (a / 2) b`
        match match_numeral eb₁ with
        -- | match_numeral_result.zero := -- `b = 1`, not reached
        | match_numeral_result.one := do -- `b = 3`
          r ← er.to_int,
          (zc, er') ← zc.of_int (- r),
          pure (zc, nc, er', `(jacobi_sym_nat.even_odd₃).mk_app [ea₁, `(0 : ℕ), er, p])
        | match_numeral_result.bit0 eb₂ := do -- `b % 4 = 1`
          match match_numeral eb₂ with
          -- | match_numeral_result.zero := -- not reached
          | match_numeral_result.one := do -- `b = 5`
            r ← er.to_int,
            (zc, er') ← zc.of_int (- r),
            pure (zc, nc, er', `(jacobi_sym_nat.even_odd₅).mk_app [ea₁, `(0 : ℕ), er, p])
          | match_numeral_result.bit0 eb₃ := do -- `b % 8 = 1`
            pure (zc, nc, er, `(jacobi_sym_nat.even_odd₁).mk_app [ea₁, eb₃, er, p])
          | match_numeral_result.bit1 eb₃ := do -- `b % 8 = 5`
            r ← er.to_int,
            (zc, er') ← zc.of_int (- r),
            pure (zc, nc, er', `(jacobi_sym_nat.even_odd₅).mk_app [ea₁, eb₃, er, p])
          | _ := failed
          end
        | match_numeral_result.bit1 eb₂ := do -- `b % 4 = 3`
          match match_numeral eb₂ with
          -- | match_numeral_result.zero := -- not reached
          | match_numeral_result.one := do -- `b = 7`
            pure (zc, nc, er, `(jacobi_sym_nat.even_odd₇).mk_app [ea₁, `(0 : ℕ), er, p])
          | match_numeral_result.bit0 eb₃ := do -- `b % 8 = 3`
            r ← er.to_int,
            (zc, er') ← zc.of_int (- r),
            pure (zc, nc, er', `(jacobi_sym_nat.even_odd₃).mk_app [ea₁, eb₃, er, p])
          | match_numeral_result.bit1 eb₃ := do -- `b % 8 = 7`
            pure (zc, nc, er, `(jacobi_sym_nat.even_odd₇).mk_app [ea₁, eb₃, er, p])
          | _ := failed
          end
        | _ := failed
        end
      end
    | match_numeral_result.bit1 ea₁ := do -- `a` is odd
      -- use Quadratic Reciprocity; look at `a` and `b` mod `4`
      (nc, bma, phab) ← prove_div_mod nc eb ea tt, -- compute `b % a`
      (zc, nc, er, p) ← prove_jacobi_sym_odd zc nc bma ea, -- compute `jacobi_sym_nat (b % a) a`
      match match_numeral ea₁ with
      -- | match_numeral_result.zero :=  -- `a = 1`, not reached
      | match_numeral_result.one := do -- `a = 3`; need to consider `b`
        match match_numeral eb₁ with
        -- | match_numeral_result.zero := -- `b = 1`, not reached
        -- | match_numeral_result.one := -- `b = 3`, not reached, since `a < b`
        | match_numeral_result.bit0 eb₂ := do -- `b % 4 = 1`
          pure (zc, nc, er, `(jacobi_sym_nat.qr₁'_mod).mk_app [ea₁, eb₂, bma, er, phab, p])
        | match_numeral_result.bit1 eb₂ := do -- `b % 4 = 3`
          r ← er.to_int,
          (zc, er') ← zc.of_int (- r),
          pure (zc, nc, er', `(jacobi_sym_nat.qr₃_mod).mk_app [`(0 : ℕ), eb₂, bma, er, phab, p])
        | _ := failed
        end
      | match_numeral_result.bit0 ea₂ := do -- `a % 4 = 1`
        pure (zc, nc, er, `(jacobi_sym_nat.qr₁_mod).mk_app [ea₂, eb₁, bma, er, phab, p])
      | match_numeral_result.bit1 ea₂ := do -- `a % 4 = 3`; need to consider `b`
        match match_numeral eb₁ with
        -- | match_numeral_result.zero := do -- `b = 1`, not reached
        -- | match_numeral_result.one := do -- `b = 3`, not reached, since `a < b`
        | match_numeral_result.bit0 eb₂ := do -- `b % 4 = 1`
          pure (zc, nc, er, `(jacobi_sym_nat.qr₁'_mod).mk_app [ea₁, eb₂, bma, er, phab, p])
        | match_numeral_result.bit1 eb₂ := do -- `b % 4 = 3`
          r ← er.to_int,
          (zc, er') ← zc.of_int (- r),
          pure (zc, nc, er', `(jacobi_sym_nat.qr₃_mod).mk_app [ea₂, eb₂, bma, er, phab, p])
        | _ := failed
        end
      | _ := failed
      end
    | _ := failed
    end
  | _ := failed
  end

/-- This evaluates `r := jacobi_sym_nat a b` and produces a proof term for the equality
by removing powers of `2` from `b` and then calling `prove_jacobi_sym_odd`. -/
meta def prove_jacobi_sym_nat : instance_cache → instance_cache → expr → expr →
   tactic (instance_cache × instance_cache × expr × expr)
| zc nc ea eb := do
  match match_numeral eb with
  | match_numeral_result.zero := -- `b = 0`, result is `1`
    pure (zc, nc, `(1 : ℤ), `(jacobi_sym_nat.zero_right).mk_app [ea])
  | match_numeral_result.one :=  -- `b = 1`, result is `1`
    pure (zc, nc, `(1 : ℤ), `(jacobi_sym_nat.one_right).mk_app [ea])
  | match_numeral_result.bit0 eb₁ := -- `b` is even and nonzero
    match match_numeral ea with
    | match_numeral_result.zero := do -- `a = 0`, result is `0`
      b ← eb₁.to_nat,
      (nc, phb₀) ← prove_ne nc eb₁ `(0 : ℕ) b 0, -- proof of `b ≠ 0`
      pure (zc, nc, `(0 : ℤ), `(jacobi_sym_nat.zero_left_even).mk_app [eb₁, phb₀])
    | match_numeral_result.one := do -- `a = 1`, result is `1`
      pure (zc, nc, `(1 : ℤ), `(jacobi_sym_nat.one_left_even).mk_app [eb₁])
    | match_numeral_result.bit0 ea₁ := do -- `a` is even, result is `0`
      b ← eb₁.to_nat,
      (nc, phb₀) ← prove_ne nc eb₁ `(0 : ℕ) b 0, -- proof of `b ≠ 0`
      let er : expr := `(0 : ℤ),
      pure (zc, nc, er, `(jacobi_sym_nat.even_even).mk_app [ea₁, eb₁, phb₀])
    | match_numeral_result.bit1 ea₁ := do -- `a` is odd, reduce to `b / 2`
      (zc, nc, er, p) ← prove_jacobi_sym_nat zc nc ea eb₁,
      pure (zc, nc, er, `(jacobi_sym_nat.odd_even).mk_app [ea₁, eb₁, er, p])
    | _ := failed
    end
  | match_numeral_result.bit1 eb₁ := do -- `b` is odd
    a ← ea.to_nat,
    b ← eb.to_nat,
    if b ≤ a then do -- reduce to `jacobi_sym_nat (a % b) b`
      (nc, amb, phab) ← prove_div_mod nc ea eb tt, -- compute `a % b`
      (zc, nc, er, p) ← prove_jacobi_sym_odd zc nc amb eb, -- compute `jacobi_sym_nat (a % b) b`
      pure (zc, nc, er, `(jacobi_sym_nat.mod_left).mk_app [ea, eb, amb, er, phab, p])
    else
    prove_jacobi_sym_odd zc nc ea eb
  | _ := failed
  end

/-- This evaluates `r := jacobi_sym a b` and produces a proof term for the equality.
This is done by reducing to `r := jacobi_sym_nat (a % b) b`. -/
meta def prove_jacobi_sym : instance_cache → instance_cache → expr → expr
    → tactic (instance_cache × instance_cache × expr × expr)
| zc nc ea eb := do
  match match_numeral eb with -- deal with simple cases right away
  | match_numeral_result.zero := pure (zc, nc, `(1 : ℤ), `(jacobi_sym.zero_right).mk_app [ea])
  | match_numeral_result.one := pure (zc, nc, `(1 : ℤ), `(jacobi_sym.one_right).mk_app [ea])
  | _ := do -- Now `1 < b`. Compute `jacobi_sym_nat (a % b) b` instead.
    b ← eb.to_nat,
    (zc, eb') ← zc.of_int (b : ℤ),
    -- Get the proof that `(b : ℤ) = b'` (where `eb'` is the numeral representing `b'`).
    -- This is important to avoid inefficient matching between the two.
    (zc, nc, eb₁, pb') ← prove_nat_uncast zc nc eb',
    (zc, amb, phab) ← prove_div_mod zc ea eb' tt, -- compute `a % b`
    (zc, nc, amb', phab') ← prove_nat_uncast zc nc amb, -- `a % b` as a natural number
    (zc, nc, er, p) ← prove_jacobi_sym_nat zc nc amb' eb₁, -- compute `jacobi_sym_nat (a % b) b`
    pure (zc, nc, er,
          `(jacobi_sym.mod_left).mk_app [ea, eb₁, amb', amb, er, eb', pb', phab, phab', p])
  end

end norm_num

end evaluation

section tactic

/-!
### The `norm_num` plug-in
-/

namespace tactic
namespace norm_num

/-- This is the `norm_num` plug-in that evaluates Jacobi and Legendre symbols. -/
@[norm_num] meta def eval_jacobi_sym : expr → tactic (expr × expr)
| `(jacobi_sym %%ea %%eb) := do -- Jacobi symbol
    zc ← mk_instance_cache `(ℤ),
    nc ← mk_instance_cache `(ℕ),
    (prod.snd ∘ prod.snd) <$> norm_num.prove_jacobi_sym zc nc ea eb
| `(norm_num.jacobi_sym_nat %%ea %%eb) := do -- Jacobi symbol on natural numbers
    zc ← mk_instance_cache `(ℤ),
    nc ← mk_instance_cache `(ℕ),
    (prod.snd ∘ prod.snd) <$> norm_num.prove_jacobi_sym_nat zc nc ea eb
| `(@legendre_sym %%ep %%inst %%ea) := do -- Legendre symbol
    zc ← mk_instance_cache `(ℤ),
    nc ← mk_instance_cache `(ℕ),
    (zc, nc, er, pf) ← norm_num.prove_jacobi_sym zc nc ea ep,
    pure (er, `(norm_num.legendre_sym.to_jacobi_sym).mk_app [ep, inst, ea, er, pf])
| _ := failed

end norm_num
end tactic

end tactic


import intro.level1 --hide
/-
We can state lemmas assuming hypotheses with similar notation as we made a lemma
dependent on natural numbers before.

The `rewrite` tactic can then be used to rewrite a hypothesis, after all we can substitute
things we know to be equal in facts we know as well as substituting into what we are trying to prove.

### Example:
You can use `rewrite` to change a hypothesis as well.
For example, if your goal state looks like this:
```
n m : ℕ
h1 : n + 1 = 7
h2 : m = n + 1
⊢ m + 2 = 9
```
then `rewrite h2 at h1` will turn `h1` into `h1 : m = 7`.

Below are two useful results you can use to finish this level.
-/

/- Axiom :
lemma add_zero : ∀ x, x + 0 = x
-/
lemma add_zero : ∀ x, x + 0 = x
:= nat.add_zero --hide
/- Axiom :
lemma one_mul : ∀ x, 1 * x = x
-/
lemma one_mul : ∀ x, 1 * x = x
:= nat.one_mul --hide

/- Hint : Click here for a hint, in case you get stuck.
Delete `sorry` and type `rw h,` (don't forget the comma!). Lean tries `refl` afterwards,
so you will see that this suffices.
Delete `sorry` and type `rewrite add_zero x at hx,` (don't forget the comma!), as a first step of the proof.
In fact, in this situation the `rewrite` tactic can infer that the argument of `add_zero` should be `x`,
so one could leave out the argument `x`, i.e. simply write `rewrite add_zero at hx,` (don't forget the comma!).
-/

/- Lemma : no-side-bar
-/
lemma level2 (x y : ℕ) (hx : x + 0 = 1 * y) : x + y = y + y :=
begin
  rw add_zero at hx,
  rw one_mul at hx,
  rw hx,

end


/-
Copyright (c) 2022 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies

! This file was ported from Lean 3 source module data.finset.finsupp
! leanprover-community/mathlib commit 59694bd07f0a39c5beccba34bd9f413a160782bf
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.BigOperators.Finsupp
import Mathlib.Data.Finset.Pointwise
import Mathlib.Data.Finsupp.Indicator
import Mathlib.Data.Fintype.BigOperators

/-!
# Finitely supported product of finsets

This file defines the finitely supported product of finsets as a `Finset (ι →₀ α)`.

## Main declarations

* `Finset.finsupp`: Finitely supported product of finsets. `s.finset t` is the product of the `t i`
  over all `i ∈ s`.
* `Finsupp.pi`: `f.pi` is the finset of `Finsupp`s whose `i`-th value lies in `f i`. This is the
  special case of `Finset.finsupp` where we take the product of the `f i` over the support of `f`.

## Implementation notes

We make heavy use of the fact that `0 : Finset α` is `{0}`. This scalar actions convention turns out
to be precisely what we want here too.
-/

noncomputable section

open Finsupp

open BigOperators Classical Pointwise

variable {ι α : Type _} [Zero α] {s : Finset ι} {f : ι →₀ α}

namespace Finset

/-- Finitely supported product of finsets. -/
protected def finsupp (s : Finset ι) (t : ι → Finset α) : Finset (ι →₀ α) :=
  (s.pi t).map ⟨indicator s, indicator_injective s⟩
#align finset.finsupp Finset.finsupp

theorem mem_finsupp_iff {t : ι → Finset α} : f ∈ s.finsupp t ↔ f.support ⊆ s ∧ ∀ i ∈ s, f i ∈ t i :=
  by
  refine' mem_map.trans ⟨_, _⟩
  · rintro ⟨f, hf, rfl⟩
    refine' ⟨support_indicator_subset _ _, fun i hi => _⟩
    convert mem_pi.1 hf i hi
    exact indicator_of_mem hi _
  · refine' fun h => ⟨fun i _ => f i, mem_pi.2 h.2, _⟩
    ext i
    exact ite_eq_left_iff.2 fun hi => (not_mem_support_iff.1 fun H => hi <| h.1 H).symm
#align finset.mem_finsupp_iff Finset.mem_finsupp_iff

/-- When `t` is supported on `s`, `f ∈ s.finsupp t` precisely means that `f` is pointwise in `t`. -/
@[simp]
theorem mem_finsupp_iff_of_support_subset {t : ι →₀ Finset α} (ht : t.support ⊆ s) :
    f ∈ s.finsupp t ↔ ∀ i, f i ∈ t i := by
  refine'
    mem_finsupp_iff.trans
      (forall_and.symm.trans <|
        forall_congr' fun i =>
          ⟨fun h => _, fun h =>
            ⟨fun hi => ht <| mem_support_iff.2 fun H => mem_support_iff.1 hi _, fun _ => h⟩⟩)
  · by_cases hi : i ∈ s
    · exact h.2 hi
    · rw [not_mem_support_iff.1 (mt h.1 hi), not_mem_support_iff.1 fun H => hi <| ht H]
      exact zero_mem_zero
  · rwa [H, mem_zero] at h
#align finset.mem_finsupp_iff_of_support_subset Finset.mem_finsupp_iff_of_support_subset

@[simp]
theorem card_finsupp (s : Finset ι) (t : ι → Finset α) :
    (s.finsupp t).card = ∏ i in s, (t i).card :=
  (card_map _).trans <| card_pi _ _
#align finset.card_finsupp Finset.card_finsupp

end Finset

open Finset

namespace Finsupp

/-- Given a finitely supported function `f : ι →₀ Finset α`, one can define the finset
`f.pi` of all finitely supported functions whose value at `i` is in `f i` for all `i`. -/
def pi (f : ι →₀ Finset α) : Finset (ι →₀ α) :=
  f.support.finsupp f
#align finsupp.pi Finsupp.pi

@[simp]
theorem mem_pi {f : ι →₀ Finset α} {g : ι →₀ α} : g ∈ f.pi ↔ ∀ i, g i ∈ f i :=
  mem_finsupp_iff_of_support_subset <| Subset.refl _
#align finsupp.mem_pi Finsupp.mem_pi

@[simp]
theorem card_pi (f : ι →₀ Finset α) : f.pi.card = f.prod fun i => (f i).card := by
  rw [pi, card_finsupp]
  exact Finset.prod_congr rfl fun i _ => by simp only [Pi.nat_apply, Nat.cast_id]
#align finsupp.card_pi Finsupp.card_pi

end Finsupp


-- Teoría de grupos
-- =====================================================================

import tactic

-- Nota técnica: Trabajamos en un espacio de nombres `oculto` porque
-- Lean ya tiene `group`. Ahora nuestra definición de grupo se llamará
-- realmente `oculto.group`.

-- ---------------------------------------------------------------------
-- Ejercicio. Abrir el espacio de nombres
-- ---------------------------------------------------------------------

namespace oculto

-- =====================================================================
-- § Definición de grupo                                              --
-- =====================================================================

-- ---------------------------------------------------------------------
-- Ejercicio. Definir la clase `group` como una extensión de las clases
-- `has_mul`, `has_one` y `has_inv` que verifica las propiedades
--    asociativa               : ∀ (a b c : G), (a * b) * c = a * (b * c)
--    neutro por la izquierda  : ∀ (a : G), 1 * a = a
--    inverso por la izquierda : ∀ (a : G), a⁻¹ * a = 1
-- ---------------------------------------------------------------------

class group (G : Type) extends has_mul G, has_one G, has_inv G :=
(mul_assoc : ∀ (a b c : G), a * b * c = a * (b * c))
(one_mul : ∀ (a : G), 1 * a = a)
(mul_left_inv : ∀ (a : G), a⁻¹ * a = 1)

-- La forma de decir "sea G un grupo" ahora es `(G : Type) [group G]`

-- Formalmente, un término de tipo `grupo G` consta de
-- + la definición de una operación interna: * : G → G → G
-- + la definición de un elemento neutro: 1 : G
-- + la definición de una operación inversa: (⁻¹) : G → G
-- + la 3 pruebas de los 3 axiomas.

-- ---------------------------------------------------------------------
-- Ejercicio. Abrir el espacio de nombres `group`
-- ---------------------------------------------------------------------

namespace group

-- ---------------------------------------------------------------------
-- Ejercicio. Definir `G` como una variable sobre grupos.
-- ---------------------------------------------------------------------

variables {G : Type} [group G]

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que en los grupos se cumple la propiedad
-- cancelativa por la izquierda
--    a * b = a * c → b = c
-- ---------------------------------------------------------------------

lemma mul_left_cancel
  (a b c : G)
  (Habac : a * b = a * c)
  : b = c :=
begin
 calc b = 1 * b         : by rw one_mul
    ... = (a⁻¹ * a) * b : by rw mul_left_inv
    ... = a⁻¹ * (a * b) : by rw mul_assoc
    ... = a⁻¹ * (a * c) : by rw Habac
    ... = (a⁻¹ * a) * c : by rw mul_assoc
    ... = 1 * c         : by rw mul_left_inv
    ... = c             : by rw one_mul
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ∀ a x y : G, x = a⁻¹ * y → a * x = y
-- ---------------------------------------------------------------------

lemma mul_eq_of_eq_inv_mul
  {a x y : G}
  (h : x = a⁻¹ * y)
  : a * x = y :=
begin
  apply mul_left_cancel a⁻¹,
  rw ←mul_assoc,
  rw mul_left_inv,
  rwa one_mul,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Definir a, b, c, x e y como variables sobre G.
-- ---------------------------------------------------------------------

variables (a b c x y : G)

-- ---------------------------------------------------------------------
-- Ejercicio [KB-R11). Demostrar que 1 es neutro por la derecha; es
-- decir,
--    a * 1 = a
-- ---------------------------------------------------------------------

@[simp] theorem mul_one :
  a * 1 = a :=
begin
  apply mul_eq_of_eq_inv_mul,
  rw mul_left_inv,
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R13). Demostrar que
--    a * a⁻¹ = 1
-- ---------------------------------------------------------------------

@[simp] theorem mul_right_inv :
  a * a⁻¹ = 1 :=
begin
  apply mul_eq_of_eq_inv_mul,
  rw mul_one,
end

-- =====================================================================
-- § Simplificador de Lean                                            --
-- =====================================================================

-- Un humano ve "a * a⁻¹" en la teoría de grupos y lo reemplaza
-- instantáneamente con "1".
--
-- Vamos a entrenar una IA simple llamada "simp" para que haga lo
-- mismo.
--
-- El simplificador de Lean `simp` es un "sistema de reescritura de
-- términos". Esto significa que si le enseñas un montón de teoremas de
-- la forma `A = B` o `P ↔ Q` (etiquetándolos con el atributo `@[simp]`)
-- y luego le das un objetivo complicado, como por ejemplo:
--    (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1
-- Lean intentará usar la táctica `rw` tanto como pueda, usando los
-- lemas que se les ha enseñado, en un intento de simplificar el
-- objetivo. Si se las arregla para resolverlo por completo, ¡entonces
-- genial! Si no es así, pero piensas que debería haberlo hecho, es
-- posible que tengas que etiquetar más lemas con `@[simp]`.
--
-- `simp` solo debe usarse para cerrar completamente los objetivos.
--
-- Ahora vamos a entrenar al simplificador para resolver el ejemplo
-- anterior (de hecho, estamos entrenándolo para reducir un elemento
-- arbitrario de un grupo libre en una forma normal única, por lo que
-- resolverá cualquier igualdad que sea verdadera para todos los grupos,
-- como en el ejemplo anterior).
--
-- Notal importante: El simplificador de Lean hace una serie de
-- reescrituras, cada una reemplazando algo con algo más simple. ¡Pero
-- el simplificador siempre reescribirá de izquierda a derecha! Si le
-- dice que `A = B` es un lema de simplificación, entonces reemplazará
-- las `A` por las `B`, pero nunca reemplazará las `B` por las `A`.
--
-- Si etiqueta una prueba de `A = B` con `@[simp]` y también etiqueta
-- una prueba de `B = A` con `@[simp]`, entonces el simplificador se
-- atascará en un bucle infinito cuando se encuentra con una "A".
--
-- La igualdad no debe considerarse aquí simétrica.
--
-- Dado que el simplificador funciona de izquierda a derecha, es
-- importante observar que si `A = B` es un lema de simplificación,
-- entonces `B` debería ser más simple que `A`.
--
-- No es una coincidencia que en los teoremas a continuación
--    `@[simp] theorem mul_one (a : G) : a * 1 = a`
--    `@[simp] theorem mul_right_inv (a : G) : a * a⁻¹ = 1`
-- el lado derecho es más simple que el lado izquierdo.
--
-- Sería un desastre etiquetar `a = a * 1` con la etiqueta
-- `@[simp]`. ¿Puedes ver por qué?
--
-- ¡Entrenemos el simplificador de Lean! Enseñémosle los axiomas de un
-- grupo a continuación. Ya hemos visto los axiomas, por lo que tenemos
-- que etiquetarlos con el atributo `@ [simp]`.

-- ---------------------------------------------------------------------
-- Ejercicio. Etiquetar como reglas de simplificación los axiomas
-- one_mul (KB-R1), mul_left_inv (KB-R2) y mul_assoc (KB-R3).
-- ---------------------------------------------------------------------

attribute [simp] one_mul mul_left_inv mul_assoc

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R4). Demostrar que
--    a⁻¹ * (a * b) = b
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  a⁻¹ * (a * b) = b :=
begin
  rw ← mul_assoc,
  simp,
end

-- 2ª demostración
@[simp] lemma inv_mul_cancel_left :
  a⁻¹ * (a * b) = b :=
begin
  rw ← mul_assoc,
  -- squeeze_simp,
  simp only [one_mul,
             mul_left_inv],
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R14). Demostrar que
--    a * (a⁻¹ * b) = b
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  a * (a⁻¹ * b) = b :=
begin
  rw ←mul_assoc,
  simp,
end

-- 2ª demostración
@[simp] lemma mul_inv_cancel_left :
  a * (a⁻¹ * b) = b :=
begin
  rw ←mul_assoc,
  -- squeeze_simp,
  simp only [one_mul,
             mul_right_inv],
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R17). Demostrar que
--    (a * b)⁻¹ = b⁻¹ * a⁻¹
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply mul_left_cancel (a * b),
  rw mul_right_inv,
  simp,
end

-- 2ª demostración
@[simp] lemma inv_mul :
  (a * b)⁻¹ = b⁻¹ * a⁻¹ :=
begin
  apply mul_left_cancel (a * b),
  rw mul_right_inv,
  -- squeeze_simp,
  simp only [mul_assoc,
             mul_inv_cancel_left,
             mul_right_inv]
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R8). Demostrar que
--    (1 : G)⁻¹ = 1
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (1 : G)⁻¹ = 1 :=
begin
  apply mul_left_cancel (1 : G),
  rw mul_right_inv,
  simp,
end

-- 1ª demostración
@[simp] lemma one_inv :
  (1 : G)⁻¹ = 1 :=
begin
  apply mul_left_cancel (1 : G),
  rw mul_right_inv,
  -- squeeze_simp,
  simp only [one_mul],
end

-- ---------------------------------------------------------------------
-- Ejercicio (KB-R12). Demostrar que
--    (a ⁻¹) ⁻¹ = a
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (a ⁻¹) ⁻¹ = a :=
begin
  apply mul_left_cancel a⁻¹,
  simp,
end

-- 2ª demostración
@[simp] lemma inv_inv :
  (a ⁻¹) ⁻¹ = a :=
begin
  apply mul_left_cancel a⁻¹,
  -- squeeze_simp,
  simp only [mul_right_inv,
             mul_left_inv],
end

-- La razón para elegir estos cinco lemas es https://bit.ly/2YyZdhi

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1
-- ---------------------------------------------------------------------

-- 1ª demostración
example :
  (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1 :=
  by simp

-- 2ª demostración
example :
  (a * b) * 1⁻¹⁻¹ * b⁻¹ * (a⁻¹ * a⁻¹⁻¹⁻¹) * a = 1 :=
-- by squeeze_simp
by simp only [mul_one,
              mul_assoc,
              mul_right_inv,
              inv_inv,
              mul_left_inv]

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a * c = b → a = b * c⁻¹
-- ---------------------------------------------------------------------

lemma eq_mul_inv_of_mul_eq
  {a b c : G}
  (h : a * c = b)
  : a = b * c⁻¹ :=
begin
  rw ← h,
  simp,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    b * a = c → a = b⁻¹ * c
-- ---------------------------------------------------------------------

lemma eq_inv_mul_of_mul_eq
  {a b c : G}
  (h : b * a = c)
  : a = b⁻¹ * c :=
begin
  rw ← h,
  simp,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a * b = b ↔ a = 1
-- ---------------------------------------------------------------------

lemma mul_left_eq_self
  {a b : G}
  : a * b = b ↔ a = 1 :=
begin
  split,
  { intro h,
    replace h := eq_mul_inv_of_mul_eq h,
    simp [h] },
  { intro h,
    rw [h, one_mul] }
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a * b = a ↔ b = 1
-- ---------------------------------------------------------------------

lemma mul_right_eq_self
  {a b : G}
  : a * b = a ↔ b = 1 :=
begin
  split,
  { intro h,
    replace h := eq_inv_mul_of_mul_eq h,
    simp [h] },
  { rintro rfl,
    simp },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a * b = 1 → a = b⁻¹
-- ---------------------------------------------------------------------

lemma eq_inv_of_mul_eq_one
  {a b : G}
  (h : a * b = 1)
  : a = b⁻¹ :=
begin
  convert eq_mul_inv_of_mul_eq h,
  simp,
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a * b = 1 → a⁻¹ = b
-- ---------------------------------------------------------------------

lemma inv_eq_of_mul_eq_one
  {a b : G}
  (h : a * b = 1)
  : a⁻¹ = b :=
begin
  replace h := eq_mul_inv_of_mul_eq h,
  simp [h],
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    ∀ x : G, e * x = x → e = 1
-- ---------------------------------------------------------------------

lemma unique_left_id {e : G} (h : ∀ x : G, e * x = x) : e = 1 :=
calc e = e * 1 : by rw mul_one
  ...  = 1     : by rw h 1

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a * b = 1 → b = a⁻¹
-- ---------------------------------------------------------------------

lemma unique_right_inv
  {a b : G}
  (h : a * b = 1)
  : b = a⁻¹ :=
begin
  apply mul_left_cancel a,
  simp [h],
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a * x = a * y ↔ x = y
-- ---------------------------------------------------------------------

lemma mul_left_cancel_iff
  (a x y : G)
  : a * x = a * y ↔ x = y :=
begin
  split,
  { apply mul_left_cancel, },
  { intro hxy,
    rwa hxy, },
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    x * a = y * a → x = y
-- ---------------------------------------------------------------------

lemma mul_right_cancel
  (a x y : G)
  (Habac : x * a = y * a)
  : x = y :=
calc x = x * 1         : by rw mul_one
  ...  = x * (a * a⁻¹) : by rw mul_right_inv
  ...  = x * a * a⁻¹   : by rw mul_assoc
  ...  = y * a * a⁻¹   : by rw Habac
  ...  = y * (a * a⁻¹) : by rw mul_assoc
  ...  = y * 1         : by rw mul_right_inv
  ...  = y             : by rw mul_one

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a⁻¹ = b⁻¹ ↔ a = b
-- ---------------------------------------------------------------------

@[simp] theorem inv_inj_iff
  {a b : G}
  : a⁻¹ = b⁻¹ ↔ a = b :=
begin
  split,
  { intro h,
    rw [← inv_inv a, h, inv_inv b], },
  { rintro rfl,
    refl }
end

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    a⁻¹ = b ↔ b⁻¹ = a
-- ---------------------------------------------------------------------

theorem inv_eq
  {a b : G}
  : a⁻¹ = b ↔ b⁻¹ = a :=
begin
  split;
  { rintro rfl,
    rw inv_inv }
end

end group

end oculto

-- =====================================================================
-- § Referencias                                                      --
-- =====================================================================

-- + Kevin Buzzard. "Formalising mathematics : workshop 2 — groups and
--   subgroups. https://bit.ly/3iaYdqM
-- + Kevin Buzzard. formalising-mathematics: week 2, Part_A_groups.lean
--   https://bit.ly/2WGkyoy
-- + Kevin Buzzard. formalising-mathematics: week 2, Part_A_groups_solutions.lean
--   https://bit.ly/3le1WGc


/-
Copyright (c) 2021 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel, Yaël Dillies
-/
import analysis.normed.group.add_torsor
import analysis.normed.group.pointwise
import analysis.normed_space.basic

/-!
# Properties of pointwise scalar multiplication of sets in normed spaces.

We explore the relationships between scalar multiplication of sets in vector spaces, and the norm.
Notably, we express arbitrary balls as rescaling of other balls, and we show that the
multiplication of bounded sets remain bounded.
-/

open metric set
open_locale pointwise topology

variables {𝕜 E : Type*} [normed_field 𝕜]

section seminormed_add_comm_group
variables [seminormed_add_comm_group E] [normed_space 𝕜 E]

theorem smul_ball {c : 𝕜} (hc : c ≠ 0) (x : E) (r : ℝ) :
  c • ball x r = ball (c • x) (‖c‖ * r) :=
begin
  ext y,
  rw mem_smul_set_iff_inv_smul_mem₀ hc,
  conv_lhs { rw ←inv_smul_smul₀ hc x },
  simp [← div_eq_inv_mul, div_lt_iff (norm_pos_iff.2 hc), mul_comm _ r, dist_smul₀],
end

lemma smul_unit_ball {c : 𝕜} (hc : c ≠ 0) : c • ball (0 : E) (1 : ℝ) = ball (0 : E) (‖c‖) :=
by rw [smul_ball hc, smul_zero, mul_one]

theorem smul_sphere' {c : 𝕜} (hc : c ≠ 0) (x : E) (r : ℝ) :
  c • sphere x r = sphere (c • x) (‖c‖ * r) :=
begin
  ext y,
  rw mem_smul_set_iff_inv_smul_mem₀ hc,
  conv_lhs { rw ←inv_smul_smul₀ hc x },
  simp only [mem_sphere, dist_smul₀, norm_inv, ← div_eq_inv_mul,
    div_eq_iff (norm_pos_iff.2 hc).ne', mul_comm r],
end

theorem smul_closed_ball' {c : 𝕜} (hc : c ≠ 0) (x : E) (r : ℝ) :
  c • closed_ball x r = closed_ball (c • x) (‖c‖ * r) :=
by simp only [← ball_union_sphere, set.smul_set_union, smul_ball hc, smul_sphere' hc]

lemma metric.bounded.smul {s : set E} (hs : bounded s) (c : 𝕜) :
  bounded (c • s) :=
begin
  obtain ⟨R, hR⟩ : ∃ (R : ℝ), ∀ x ∈ s, ‖x‖ ≤ R := hs.exists_norm_le,
  refine bounded_iff_forall_norm_le.2 ⟨‖c‖ * R, λ z hz, _⟩,
  obtain ⟨y, ys, rfl⟩ : ∃ (y : E), y ∈ s ∧ c • y = z := mem_smul_set.1 hz,
  calc ‖c • y‖ = ‖c‖ * ‖y‖ : norm_smul _ _
  ... ≤ ‖c‖ * R : mul_le_mul_of_nonneg_left (hR y ys) (norm_nonneg _)
end

/-- If `s` is a bounded set, then for small enough `r`, the set `{x} + r • s` is contained in any
fixed neighborhood of `x`. -/
lemma eventually_singleton_add_smul_subset
  {x : E} {s : set E} (hs : bounded s) {u : set E} (hu : u ∈ 𝓝 x) :
  ∀ᶠ r in 𝓝 (0 : 𝕜), {x} + r • s ⊆ u :=
begin
  obtain ⟨ε, εpos, hε⟩ : ∃ ε (hε : 0 < ε), closed_ball x ε ⊆ u :=
    nhds_basis_closed_ball.mem_iff.1 hu,
  obtain ⟨R, Rpos, hR⟩ : ∃ (R : ℝ), 0 < R ∧ s ⊆ closed_ball 0 R := hs.subset_ball_lt 0 0,
  have : metric.closed_ball (0 : 𝕜) (ε / R) ∈ 𝓝 (0 : 𝕜) :=
    closed_ball_mem_nhds _ (div_pos εpos Rpos),
  filter_upwards [this] with r hr,
  simp only [image_add_left, singleton_add],
  assume y hy,
  obtain ⟨z, zs, hz⟩ : ∃ (z : E), z ∈ s ∧ r • z = -x + y, by simpa [mem_smul_set] using hy,
  have I : ‖r • z‖ ≤ ε := calc
    ‖r • z‖ = ‖r‖ * ‖z‖ : norm_smul _ _
    ... ≤ (ε / R) * R :
      mul_le_mul (mem_closed_ball_zero_iff.1 hr)
        (mem_closed_ball_zero_iff.1 (hR zs)) (norm_nonneg _) (div_pos εpos Rpos).le
    ... = ε : by field_simp [Rpos.ne'],
  have : y = x + r • z, by simp only [hz, add_neg_cancel_left],
  apply hε,
  simpa only [this, dist_eq_norm, add_sub_cancel', mem_closed_ball] using I,
end

variables [normed_space ℝ E] {x y z : E} {δ ε : ℝ}

/-- In a real normed space, the image of the unit ball under scalar multiplication by a positive
constant `r` is the ball of radius `r`. -/
lemma smul_unit_ball_of_pos {r : ℝ} (hr : 0 < r) : r • ball 0 1 = ball (0 : E) r :=
by rw [smul_unit_ball hr.ne', real.norm_of_nonneg hr.le]

-- This is also true for `ℚ`-normed spaces
lemma exists_dist_eq (x z : E) {a b : ℝ} (ha : 0 ≤ a) (hb : 0 ≤ b) (hab : a + b = 1) :
  ∃ y, dist x y = b * dist x z ∧ dist y z = a * dist x z :=
begin
  use a • x + b • z,
  nth_rewrite 0 [←one_smul ℝ x],
  nth_rewrite 3 [←one_smul ℝ z],
  simp [dist_eq_norm, ←hab, add_smul, ←smul_sub, norm_smul_of_nonneg, ha, hb],
end

lemma exists_dist_le_le (hδ : 0 ≤ δ) (hε : 0 ≤ ε) (h : dist x z ≤ ε + δ) :
  ∃ y, dist x y ≤ δ ∧ dist y z ≤ ε :=
begin
  obtain rfl | hε' := hε.eq_or_lt,
  { exact ⟨z, by rwa zero_add at h, (dist_self _).le⟩ },
  have hεδ := add_pos_of_pos_of_nonneg hε' hδ,
  refine (exists_dist_eq x z (div_nonneg hε $ add_nonneg hε hδ) (div_nonneg hδ $ add_nonneg hε hδ) $
    by rw [←add_div, div_self hεδ.ne']).imp (λ y hy, _),
  rw [hy.1, hy.2, div_mul_comm, div_mul_comm ε],
  rw ←div_le_one hεδ at h,
  exact ⟨mul_le_of_le_one_left hδ h, mul_le_of_le_one_left hε h⟩,
end

-- This is also true for `ℚ`-normed spaces
lemma exists_dist_le_lt (hδ : 0 ≤ δ) (hε : 0 < ε) (h : dist x z < ε + δ) :
  ∃ y, dist x y ≤ δ ∧ dist y z < ε :=
begin
  refine (exists_dist_eq x z (div_nonneg hε.le $ add_nonneg hε.le hδ) (div_nonneg hδ $ add_nonneg
    hε.le hδ) $ by rw [←add_div, div_self (add_pos_of_pos_of_nonneg hε hδ).ne']).imp (λ y hy, _),
  rw [hy.1, hy.2, div_mul_comm, div_mul_comm ε],
  rw ←div_lt_one (add_pos_of_pos_of_nonneg hε hδ) at h,
  exact ⟨mul_le_of_le_one_left hδ h.le, mul_lt_of_lt_one_left hε h⟩,
end

-- This is also true for `ℚ`-normed spaces
lemma exists_dist_lt_le (hδ : 0 < δ) (hε : 0 ≤ ε) (h : dist x z < ε + δ) :
  ∃ y, dist x y < δ ∧ dist y z ≤ ε :=
begin
  obtain ⟨y, yz, xy⟩ := exists_dist_le_lt hε hδ
    (show dist z x < δ + ε, by simpa only [dist_comm, add_comm] using h),
  exact ⟨y, by simp [dist_comm x y, dist_comm y z, *]⟩,
end

-- This is also true for `ℚ`-normed spaces
lemma exists_dist_lt_lt (hδ : 0 < δ) (hε : 0 < ε) (h : dist x z < ε + δ) :
  ∃ y, dist x y < δ ∧ dist y z < ε :=
begin
  refine (exists_dist_eq x z (div_nonneg hε.le $ add_nonneg hε.le hδ.le) (div_nonneg hδ.le $
    add_nonneg hε.le hδ.le) $ by rw [←add_div, div_self (add_pos hε hδ).ne']).imp (λ y hy, _),
  rw [hy.1, hy.2, div_mul_comm, div_mul_comm ε],
  rw ←div_lt_one (add_pos hε hδ) at h,
  exact ⟨mul_lt_of_lt_one_left hδ h, mul_lt_of_lt_one_left hε h⟩,
end

-- This is also true for `ℚ`-normed spaces
lemma disjoint_ball_ball_iff (hδ : 0 < δ) (hε : 0 < ε) :
  disjoint (ball x δ) (ball y ε) ↔ δ + ε ≤ dist x y :=
begin
  refine ⟨λ h, le_of_not_lt $ λ hxy, _, ball_disjoint_ball⟩,
  rw add_comm at hxy,
  obtain ⟨z, hxz, hzy⟩ := exists_dist_lt_lt hδ hε hxy,
  rw dist_comm at hxz,
  exact h.le_bot ⟨hxz, hzy⟩,
end

-- This is also true for `ℚ`-normed spaces
lemma disjoint_ball_closed_ball_iff (hδ : 0 < δ) (hε : 0 ≤ ε) :
  disjoint (ball x δ) (closed_ball y ε) ↔ δ + ε ≤ dist x y :=
begin
  refine ⟨λ h, le_of_not_lt $ λ hxy, _, ball_disjoint_closed_ball⟩,
  rw add_comm at hxy,
  obtain ⟨z, hxz, hzy⟩ := exists_dist_lt_le hδ hε hxy,
  rw dist_comm at hxz,
  exact h.le_bot ⟨hxz, hzy⟩,
end

-- This is also true for `ℚ`-normed spaces
lemma disjoint_closed_ball_ball_iff (hδ : 0 ≤ δ) (hε : 0 < ε) :
  disjoint (closed_ball x δ) (ball y ε) ↔ δ + ε ≤ dist x y :=
by rw [disjoint.comm, disjoint_ball_closed_ball_iff hε hδ, add_comm, dist_comm]; apply_instance

lemma disjoint_closed_ball_closed_ball_iff (hδ : 0 ≤ δ) (hε : 0 ≤ ε) :
  disjoint (closed_ball x δ) (closed_ball y ε) ↔ δ + ε < dist x y :=
begin
  refine ⟨λ h, lt_of_not_ge $ λ hxy, _, closed_ball_disjoint_closed_ball⟩,
  rw add_comm at hxy,
  obtain ⟨z, hxz, hzy⟩ := exists_dist_le_le hδ hε hxy,
  rw dist_comm at hxz,
  exact h.le_bot ⟨hxz, hzy⟩,
end

open emetric ennreal

@[simp] lemma inf_edist_thickening (hδ : 0 < δ) (s : set E) (x : E) :
  inf_edist x (thickening δ s) = inf_edist x s - ennreal.of_real δ :=
begin
  obtain hs | hs := lt_or_le (inf_edist x s) (ennreal.of_real δ),
  { rw [inf_edist_zero_of_mem, tsub_eq_zero_of_le hs.le], exact hs },
  refine (tsub_le_iff_right.2 inf_edist_le_inf_edist_thickening_add).antisymm' _,
  refine le_sub_of_add_le_right of_real_ne_top _,
  refine le_inf_edist.2 (λ z hz, le_of_forall_lt' $ λ r h, _),
  cases r,
  { exact add_lt_top.2 ⟨lt_top_iff_ne_top.2 $ inf_edist_ne_top ⟨z, self_subset_thickening hδ _ hz⟩,
      of_real_lt_top⟩ },
  have hr : 0 < ↑r - δ,
  { refine sub_pos_of_lt _,
    have := hs.trans_lt ((inf_edist_le_edist_of_mem hz).trans_lt h),
    rw [of_real_eq_coe_nnreal hδ.le, some_eq_coe] at this,
    exact_mod_cast this },
  rw [some_eq_coe, edist_lt_coe, ←dist_lt_coe, ←add_sub_cancel'_right δ (↑r)] at h,
  obtain ⟨y, hxy, hyz⟩ := exists_dist_lt_lt hr hδ h,
  refine (ennreal.add_lt_add_right of_real_ne_top $ inf_edist_lt_iff.2
    ⟨_, mem_thickening_iff.2 ⟨_, hz, hyz⟩, edist_lt_of_real.2 hxy⟩).trans_le _,
  rw [←of_real_add hr.le hδ.le, sub_add_cancel, of_real_coe_nnreal],
  exact le_rfl,
end

@[simp] lemma thickening_thickening (hε : 0 < ε) (hδ : 0 < δ) (s : set E) :
  thickening ε (thickening δ s) = thickening (ε + δ) s :=
(thickening_thickening_subset _ _ _).antisymm $ λ x, begin
  simp_rw mem_thickening_iff,
  rintro ⟨z, hz, hxz⟩,
  rw add_comm at hxz,
  obtain ⟨y, hxy, hyz⟩ := exists_dist_lt_lt hε hδ hxz,
  exact ⟨y, ⟨_, hz, hyz⟩, hxy⟩,
end

@[simp] lemma cthickening_thickening (hε : 0 ≤ ε) (hδ : 0 < δ) (s : set E) :
  cthickening ε (thickening δ s) = cthickening (ε + δ) s :=
(cthickening_thickening_subset hε _ _).antisymm $ λ x, begin
  simp_rw [mem_cthickening_iff, ennreal.of_real_add hε hδ.le, inf_edist_thickening hδ],
  exact tsub_le_iff_right.2,
end

-- Note: `interior (cthickening δ s) ≠ thickening δ s` in general
@[simp] lemma closure_thickening (hδ : 0 < δ) (s : set E) :
  closure (thickening δ s) = cthickening δ s :=
by { rw [←cthickening_zero, cthickening_thickening le_rfl hδ, zero_add], apply_instance }

@[simp] lemma inf_edist_cthickening (δ : ℝ) (s : set E) (x : E) :
  inf_edist x (cthickening δ s) = inf_edist x s - ennreal.of_real δ :=
begin
  obtain hδ | hδ := le_or_lt δ 0,
  { rw [cthickening_of_nonpos hδ, inf_edist_closure, of_real_of_nonpos hδ, tsub_zero] },
  { rw [←closure_thickening hδ, inf_edist_closure, inf_edist_thickening hδ]; apply_instance }
end

@[simp] lemma thickening_cthickening (hε : 0 < ε) (hδ : 0 ≤ δ) (s : set E) :
  thickening ε (cthickening δ s) = thickening (ε + δ) s :=
begin
  obtain rfl | hδ := hδ.eq_or_lt,
  { rw [cthickening_zero, thickening_closure, add_zero] },
  { rw [←closure_thickening hδ, thickening_closure, thickening_thickening hε hδ]; apply_instance }
end

@[simp] lemma cthickening_cthickening (hε : 0 ≤ ε) (hδ : 0 ≤ δ) (s : set E) :
  cthickening ε (cthickening δ s) = cthickening (ε + δ) s :=
(cthickening_cthickening_subset hε hδ _).antisymm $ λ x, begin
  simp_rw [mem_cthickening_iff, ennreal.of_real_add hε hδ, inf_edist_cthickening],
  exact tsub_le_iff_right.2,
end

@[simp] lemma thickening_ball (hε : 0 < ε) (hδ : 0 < δ) (x : E) :
  thickening ε (ball x δ) = ball x (ε + δ) :=
by rw [←thickening_singleton, thickening_thickening hε hδ, thickening_singleton]; apply_instance

@[simp] lemma thickening_closed_ball (hε : 0 < ε) (hδ : 0 ≤ δ) (x : E) :
  thickening ε (closed_ball x δ) = ball x (ε + δ) :=
by rw [←cthickening_singleton _ hδ, thickening_cthickening hε hδ, thickening_singleton];
  apply_instance

@[simp] lemma cthickening_ball (hε : 0 ≤ ε) (hδ : 0 < δ) (x : E) :
  cthickening ε (ball x δ) = closed_ball x (ε + δ) :=
by rw [←thickening_singleton, cthickening_thickening hε hδ,
  cthickening_singleton _ (add_nonneg hε hδ.le)]; apply_instance

@[simp] lemma cthickening_closed_ball (hε : 0 ≤ ε) (hδ : 0 ≤ δ) (x : E) :
  cthickening ε (closed_ball x δ) = closed_ball x (ε + δ) :=
by rw [←cthickening_singleton _ hδ, cthickening_cthickening hε hδ,
  cthickening_singleton _ (add_nonneg hε hδ)]; apply_instance

lemma ball_add_ball (hε : 0 < ε) (hδ : 0 < δ) (a b : E) :
  ball a ε + ball b δ = ball (a + b) (ε + δ) :=
by rw [ball_add, thickening_ball hε hδ b, metric.vadd_ball, vadd_eq_add]

lemma ball_sub_ball (hε : 0 < ε) (hδ : 0 < δ) (a b : E) :
  ball a ε - ball b δ = ball (a - b) (ε + δ) :=
by simp_rw [sub_eq_add_neg, neg_ball, ball_add_ball hε hδ]

lemma ball_add_closed_ball (hε : 0 < ε) (hδ : 0 ≤ δ) (a b : E) :
  ball a ε + closed_ball b δ = ball (a + b) (ε + δ) :=
by rw [ball_add, thickening_closed_ball hε hδ b, metric.vadd_ball, vadd_eq_add]

lemma ball_sub_closed_ball (hε : 0 < ε) (hδ : 0 ≤ δ) (a b : E) :
  ball a ε - closed_ball b δ = ball (a - b) (ε + δ) :=
by simp_rw [sub_eq_add_neg, neg_closed_ball, ball_add_closed_ball hε hδ]

lemma closed_ball_add_ball (hε : 0 ≤ ε) (hδ : 0 < δ) (a b : E) :
  closed_ball a ε + ball b δ = ball (a + b) (ε + δ) :=
by rw [add_comm, ball_add_closed_ball hδ hε b, add_comm, add_comm δ]

lemma closed_ball_sub_ball (hε : 0 ≤ ε) (hδ : 0 < δ) (a b : E) :
  closed_ball a ε - ball b δ = ball (a - b) (ε + δ) :=
by simp_rw [sub_eq_add_neg, neg_ball, closed_ball_add_ball hε hδ]

lemma closed_ball_add_closed_ball [proper_space E] (hε : 0 ≤ ε) (hδ : 0 ≤ δ) (a b : E) :
  closed_ball a ε + closed_ball b δ = closed_ball (a + b) (ε + δ) :=
by rw [(is_compact_closed_ball _ _).add_closed_ball hδ b, cthickening_closed_ball hδ hε a,
  metric.vadd_closed_ball, vadd_eq_add, add_comm, add_comm δ]

lemma closed_ball_sub_closed_ball [proper_space E] (hε : 0 ≤ ε) (hδ : 0 ≤ δ) (a b : E) :
  closed_ball a ε - closed_ball b δ = closed_ball (a - b) (ε + δ) :=
by simp_rw [sub_eq_add_neg, neg_closed_ball, closed_ball_add_closed_ball hε hδ]

end seminormed_add_comm_group

section normed_add_comm_group
variables [normed_add_comm_group E] [normed_space 𝕜 E]

theorem smul_closed_ball (c : 𝕜) (x : E) {r : ℝ} (hr : 0 ≤ r) :
  c • closed_ball x r = closed_ball (c • x) (‖c‖ * r) :=
begin
  rcases eq_or_ne c 0 with rfl|hc,
  { simp [hr, zero_smul_set, set.singleton_zero, ← nonempty_closed_ball] },
  { exact smul_closed_ball' hc x r }
end

lemma smul_closed_unit_ball (c : 𝕜) : c • closed_ball (0 : E) (1 : ℝ) = closed_ball (0 : E) (‖c‖) :=
by rw [smul_closed_ball _ _ zero_le_one, smul_zero, mul_one]

variables [normed_space ℝ E]

/-- In a real normed space, the image of the unit closed ball under multiplication by a nonnegative
number `r` is the closed ball of radius `r` with center at the origin. -/
lemma smul_closed_unit_ball_of_nonneg {r : ℝ} (hr : 0 ≤ r) :
  r • closed_ball 0 1 = closed_ball (0 : E) r :=
by rw [smul_closed_unit_ball, real.norm_of_nonneg hr]

/-- In a nontrivial real normed space, a sphere is nonempty if and only if its radius is
nonnegative. -/
@[simp] lemma normed_space.sphere_nonempty [nontrivial E] {x : E} {r : ℝ} :
  (sphere x r).nonempty ↔ 0 ≤ r :=
begin
  obtain ⟨y, hy⟩ := exists_ne x,
  refine ⟨λ h, nonempty_closed_ball.1 (h.mono sphere_subset_closed_ball), λ hr,
    ⟨r • ‖y - x‖⁻¹ • (y - x) + x, _⟩⟩,
  have : ‖y - x‖ ≠ 0, by simpa [sub_eq_zero],
  simp [norm_smul, this, real.norm_of_nonneg hr],
end

lemma smul_sphere [nontrivial E] (c : 𝕜) (x : E) {r : ℝ} (hr : 0 ≤ r) :
  c • sphere x r = sphere (c • x) (‖c‖ * r) :=
begin
  rcases eq_or_ne c 0 with rfl|hc,
  { simp [zero_smul_set, set.singleton_zero, hr] },
  { exact smul_sphere' hc x r }
end

/-- Any ball `metric.ball x r`, `0 < r` is the image of the unit ball under `λ y, x + r • y`. -/
lemma affinity_unit_ball {r : ℝ} (hr : 0 < r) (x : E) : x +ᵥ r • ball 0 1 = ball x r :=
by rw [smul_unit_ball_of_pos hr, vadd_ball_zero]

/-- Any closed ball `metric.closed_ball x r`, `0 ≤ r` is the image of the unit closed ball under
`λ y, x + r • y`. -/
lemma affinity_unit_closed_ball {r : ℝ} (hr : 0 ≤ r) (x : E) :
  x +ᵥ r • closed_ball 0 1 = closed_ball x r :=
by rw [smul_closed_unit_ball, real.norm_of_nonneg hr, vadd_closed_ball_zero]

end normed_add_comm_group


/-
Copyright (c) 2019 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad

! This file was ported from Lean 3 source module order.filter.partial
! leanprover-community/mathlib commit 0a0ec35061ed9960bf0e7ffb0335f44447b58977
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Order.Filter.Basic
import Mathbin.Data.Pfun

/-!
# `tendsto` for relations and partial functions

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

This file generalizes `filter` definitions from functions to partial functions and relations.

## Considering functions and partial functions as relations

A function `f : α → β` can be considered as the relation `rel α β` which relates `x` and `f x` for
all `x`, and nothing else. This relation is called `function.graph f`.

A partial function `f : α →. β` can be considered as the relation `rel α β` which relates `x` and
`f x` for all `x` for which `f x` exists, and nothing else. This relation is called
`pfun.graph' f`.

In this regard, a function is a relation for which every element in `α` is related to exactly one
element in `β` and a partial function is a relation for which every element in `α` is related to at
most one element in `β`.

This file leverages this analogy to generalize `filter` definitions from functions to partial
functions and relations.

## Notes

`set.preimage` can be generalized to relations in two ways:
* `rel.preimage` returns the image of the set under the inverse relation.
* `rel.core` returns the set of elements that are only related to those in the set.
Both generalizations are sensible in the context of filters, so `filter.comap` and `filter.tendsto`
get two generalizations each.

We first take care of relations. Then the definitions for partial functions are taken as special
cases of the definitions for relations.
-/

universe u v w

namespace Filter

variable {α : Type u} {β : Type v} {γ : Type w}

open Filter

/-! ### Relations -/

#print Filter.rmap /-
/-- The forward map of a filter under a relation. Generalization of `filter.map` to relations. Note
that `rel.core` generalizes `set.preimage`. -/
def rmap (r : Rel α β) (l : Filter α) : Filter β
    where
  sets := { s | r.core s ∈ l }
  univ_sets := by simp
  sets_of_superset s t hs st := mem_of_superset hs <| Rel.core_mono _ st
  inter_sets s t hs ht := by simp [Rel.core_inter, inter_mem hs ht]
#align filter.rmap Filter.rmap
-/

#print Filter.rmap_sets /-
theorem rmap_sets (r : Rel α β) (l : Filter α) : (l.rmap r).sets = r.core ⁻¹' l.sets :=
  rfl
#align filter.rmap_sets Filter.rmap_sets
-/

#print Filter.mem_rmap /-
@[simp]
theorem mem_rmap (r : Rel α β) (l : Filter α) (s : Set β) : s ∈ l.rmap r ↔ r.core s ∈ l :=
  Iff.rfl
#align filter.mem_rmap Filter.mem_rmap
-/

#print Filter.rmap_rmap /-
@[simp]
theorem rmap_rmap (r : Rel α β) (s : Rel β γ) (l : Filter α) :
    rmap s (rmap r l) = rmap (r.comp s) l :=
  filter_eq <| by simp [rmap_sets, Set.preimage, Rel.core_comp]
#align filter.rmap_rmap Filter.rmap_rmap
-/

#print Filter.rmap_compose /-
@[simp]
theorem rmap_compose (r : Rel α β) (s : Rel β γ) : rmap s ∘ rmap r = rmap (r.comp s) :=
  funext <| rmap_rmap _ _
#align filter.rmap_compose Filter.rmap_compose
-/

#print Filter.Rtendsto /-
/-- Generic "limit of a relation" predicate. `rtendsto r l₁ l₂` asserts that for every
`l₂`-neighborhood `a`, the `r`-core of `a` is an `l₁`-neighborhood. One generalization of
`filter.tendsto` to relations. -/
def Rtendsto (r : Rel α β) (l₁ : Filter α) (l₂ : Filter β) :=
  l₁.rmap r ≤ l₂
#align filter.rtendsto Filter.Rtendsto
-/

#print Filter.rtendsto_def /-
theorem rtendsto_def (r : Rel α β) (l₁ : Filter α) (l₂ : Filter β) :
    Rtendsto r l₁ l₂ ↔ ∀ s ∈ l₂, r.core s ∈ l₁ :=
  Iff.rfl
#align filter.rtendsto_def Filter.rtendsto_def
-/

#print Filter.rcomap /-
/-- One way of taking the inverse map of a filter under a relation. One generalization of
`filter.comap` to relations. Note that `rel.core` generalizes `set.preimage`. -/
def rcomap (r : Rel α β) (f : Filter β) : Filter α
    where
  sets := Rel.image (fun s t => r.core s ⊆ t) f.sets
  univ_sets := ⟨Set.univ, univ_mem, Set.subset_univ _⟩
  sets_of_superset := fun a b ⟨a', ha', ma'a⟩ ab => ⟨a', ha', ma'a.trans ab⟩
  inter_sets := fun a b ⟨a', ha₁, ha₂⟩ ⟨b', hb₁, hb₂⟩ =>
    ⟨a' ∩ b', inter_mem ha₁ hb₁, (r.core_inter a' b').Subset.trans (Set.inter_subset_inter ha₂ hb₂)⟩
#align filter.rcomap Filter.rcomap
-/

#print Filter.rcomap_sets /-
theorem rcomap_sets (r : Rel α β) (f : Filter β) :
    (rcomap r f).sets = Rel.image (fun s t => r.core s ⊆ t) f.sets :=
  rfl
#align filter.rcomap_sets Filter.rcomap_sets
-/

#print Filter.rcomap_rcomap /-
theorem rcomap_rcomap (r : Rel α β) (s : Rel β γ) (l : Filter γ) :
    rcomap r (rcomap s l) = rcomap (r.comp s) l :=
  filter_eq <| by
    ext t; simp [rcomap_sets, Rel.image, Rel.core_comp]; constructor
    · rintro ⟨u, ⟨v, vsets, hv⟩, h⟩
      exact ⟨v, vsets, Set.Subset.trans (Rel.core_mono _ hv) h⟩
    rintro ⟨t, tsets, ht⟩
    exact ⟨Rel.core s t, ⟨t, tsets, Set.Subset.rfl⟩, ht⟩
#align filter.rcomap_rcomap Filter.rcomap_rcomap
-/

#print Filter.rcomap_compose /-
@[simp]
theorem rcomap_compose (r : Rel α β) (s : Rel β γ) : rcomap r ∘ rcomap s = rcomap (r.comp s) :=
  funext <| rcomap_rcomap _ _
#align filter.rcomap_compose Filter.rcomap_compose
-/

/- warning: filter.rtendsto_iff_le_rcomap -> Filter.rtendsto_iff_le_rcomap is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (r : Rel.{u1, u2} α β) (l₁ : Filter.{u1} α) (l₂ : Filter.{u2} β), Iff (Filter.Rtendsto.{u1, u2} α β r l₁ l₂) (LE.le.{u1} (Filter.{u1} α) (Preorder.toLE.{u1} (Filter.{u1} α) (PartialOrder.toPreorder.{u1} (Filter.{u1} α) (Filter.partialOrder.{u1} α))) l₁ (Filter.rcomap.{u1, u2} α β r l₂))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} (r : Rel.{u1, u2} α β) (l₁ : Filter.{u1} α) (l₂ : Filter.{u2} β), Iff (Filter.Rtendsto.{u1, u2} α β r l₁ l₂) (LE.le.{u1} (Filter.{u1} α) (Preorder.toLE.{u1} (Filter.{u1} α) (PartialOrder.toPreorder.{u1} (Filter.{u1} α) (Filter.instPartialOrderFilter.{u1} α))) l₁ (Filter.rcomap.{u1, u2} α β r l₂))
Case conversion may be inaccurate. Consider using '#align filter.rtendsto_iff_le_rcomap Filter.rtendsto_iff_le_rcomapₓ'. -/
theorem rtendsto_iff_le_rcomap (r : Rel α β) (l₁ : Filter α) (l₂ : Filter β) :
    Rtendsto r l₁ l₂ ↔ l₁ ≤ l₂.rcomap r :=
  by
  rw [rtendsto_def]
  change (∀ s : Set β, s ∈ l₂.sets → r.core s ∈ l₁) ↔ l₁ ≤ rcomap r l₂
  simp [Filter.le_def, rcomap, Rel.mem_image]; constructor
  · exact fun h s t tl₂ => mem_of_superset (h t tl₂)
  · exact fun h t tl₂ => h _ t tl₂ Set.Subset.rfl
#align filter.rtendsto_iff_le_rcomap Filter.rtendsto_iff_le_rcomap

#print Filter.rcomap' /-
-- Interestingly, there does not seem to be a way to express this relation using a forward map.
-- Given a filter `f` on `α`, we want a filter `f'` on `β` such that `r.preimage s ∈ f` if
-- and only if `s ∈ f'`. But the intersection of two sets satisfying the lhs may be empty.
/-- One way of taking the inverse map of a filter under a relation. Generalization of `filter.comap`
to relations. -/
def rcomap' (r : Rel α β) (f : Filter β) : Filter α
    where
  sets := Rel.image (fun s t => r.Preimage s ⊆ t) f.sets
  univ_sets := ⟨Set.univ, univ_mem, Set.subset_univ _⟩
  sets_of_superset := fun a b ⟨a', ha', ma'a⟩ ab => ⟨a', ha', ma'a.trans ab⟩
  inter_sets := fun a b ⟨a', ha₁, ha₂⟩ ⟨b', hb₁, hb₂⟩ =>
    ⟨a' ∩ b', inter_mem ha₁ hb₁,
      (@Rel.preimage_inter _ _ r _ _).trans (Set.inter_subset_inter ha₂ hb₂)⟩
#align filter.rcomap' Filter.rcomap'
-/

/- warning: filter.mem_rcomap' -> Filter.mem_rcomap' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (r : Rel.{u1, u2} α β) (l : Filter.{u2} β) (s : Set.{u1} α), Iff (Membership.Mem.{u1, u1} (Set.{u1} α) (Filter.{u1} α) (Filter.hasMem.{u1} α) s (Filter.rcomap'.{u1, u2} α β r l)) (Exists.{succ u2} (Set.{u2} β) (fun (t : Set.{u2} β) => Exists.{0} (Membership.Mem.{u2, u2} (Set.{u2} β) (Filter.{u2} β) (Filter.hasMem.{u2} β) t l) (fun (H : Membership.Mem.{u2, u2} (Set.{u2} β) (Filter.{u2} β) (Filter.hasMem.{u2} β) t l) => HasSubset.Subset.{u1} (Set.{u1} α) (Set.hasSubset.{u1} α) (Rel.preimage.{u1, u2} α β r t) s)))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} (r : Rel.{u1, u2} α β) (l : Filter.{u2} β) (s : Set.{u1} α), Iff (Membership.mem.{u1, u1} (Set.{u1} α) (Filter.{u1} α) (instMembershipSetFilter.{u1} α) s (Filter.rcomap'.{u1, u2} α β r l)) (Exists.{succ u2} (Set.{u2} β) (fun (t : Set.{u2} β) => And (Membership.mem.{u2, u2} (Set.{u2} β) (Filter.{u2} β) (instMembershipSetFilter.{u2} β) t l) (HasSubset.Subset.{u1} (Set.{u1} α) (Set.instHasSubsetSet.{u1} α) (Rel.preimage.{u1, u2} α β r t) s)))
Case conversion may be inaccurate. Consider using '#align filter.mem_rcomap' Filter.mem_rcomap'ₓ'. -/
@[simp]
theorem mem_rcomap' (r : Rel α β) (l : Filter β) (s : Set α) :
    s ∈ l.rcomap' r ↔ ∃ t ∈ l, r.Preimage t ⊆ s :=
  Iff.rfl
#align filter.mem_rcomap' Filter.mem_rcomap'

#print Filter.rcomap'_sets /-
theorem rcomap'_sets (r : Rel α β) (f : Filter β) :
    (rcomap' r f).sets = Rel.image (fun s t => r.Preimage s ⊆ t) f.sets :=
  rfl
#align filter.rcomap'_sets Filter.rcomap'_sets
-/

#print Filter.rcomap'_rcomap' /-
@[simp]
theorem rcomap'_rcomap' (r : Rel α β) (s : Rel β γ) (l : Filter γ) :
    rcomap' r (rcomap' s l) = rcomap' (r.comp s) l :=
  Filter.ext fun t => by
    simp [rcomap'_sets, Rel.image, Rel.preimage_comp]; constructor
    · rintro ⟨u, ⟨v, vsets, hv⟩, h⟩
      exact ⟨v, vsets, (Rel.preimage_mono _ hv).trans h⟩
    rintro ⟨t, tsets, ht⟩
    exact ⟨s.preimage t, ⟨t, tsets, Set.Subset.rfl⟩, ht⟩
#align filter.rcomap'_rcomap' Filter.rcomap'_rcomap'
-/

#print Filter.rcomap'_compose /-
@[simp]
theorem rcomap'_compose (r : Rel α β) (s : Rel β γ) : rcomap' r ∘ rcomap' s = rcomap' (r.comp s) :=
  funext <| rcomap'_rcomap' _ _
#align filter.rcomap'_compose Filter.rcomap'_compose
-/

#print Filter.Rtendsto' /-
/-- Generic "limit of a relation" predicate. `rtendsto' r l₁ l₂` asserts that for every
`l₂`-neighborhood `a`, the `r`-preimage of `a` is an `l₁`-neighborhood. One generalization of
`filter.tendsto` to relations. -/
def Rtendsto' (r : Rel α β) (l₁ : Filter α) (l₂ : Filter β) :=
  l₁ ≤ l₂.rcomap' r
#align filter.rtendsto' Filter.Rtendsto'
-/

#print Filter.rtendsto'_def /-
theorem rtendsto'_def (r : Rel α β) (l₁ : Filter α) (l₂ : Filter β) :
    Rtendsto' r l₁ l₂ ↔ ∀ s ∈ l₂, r.Preimage s ∈ l₁ :=
  by
  unfold rtendsto' rcomap'; simp [le_def, Rel.mem_image]; constructor
  · exact fun h s hs => h _ _ hs Set.Subset.rfl
  · exact fun h s t ht => mem_of_superset (h t ht)
#align filter.rtendsto'_def Filter.rtendsto'_def
-/

#print Filter.tendsto_iff_rtendsto /-
theorem tendsto_iff_rtendsto (l₁ : Filter α) (l₂ : Filter β) (f : α → β) :
    Tendsto f l₁ l₂ ↔ Rtendsto (Function.graph f) l₁ l₂ := by
  simp [tendsto_def, Function.graph, rtendsto_def, Rel.core, Set.preimage]
#align filter.tendsto_iff_rtendsto Filter.tendsto_iff_rtendsto
-/

#print Filter.tendsto_iff_rtendsto' /-
theorem tendsto_iff_rtendsto' (l₁ : Filter α) (l₂ : Filter β) (f : α → β) :
    Tendsto f l₁ l₂ ↔ Rtendsto' (Function.graph f) l₁ l₂ := by
  simp [tendsto_def, Function.graph, rtendsto'_def, Rel.preimage_def, Set.preimage]
#align filter.tendsto_iff_rtendsto' Filter.tendsto_iff_rtendsto'
-/

/-! ### Partial functions -/

#print Filter.pmap /-
/-- The forward map of a filter under a partial function. Generalization of `filter.map` to partial
functions. -/
def pmap (f : α →. β) (l : Filter α) : Filter β :=
  Filter.rmap f.graph' l
#align filter.pmap Filter.pmap
-/

#print Filter.mem_pmap /-
@[simp]
theorem mem_pmap (f : α →. β) (l : Filter α) (s : Set β) : s ∈ l.pmap f ↔ f.core s ∈ l :=
  Iff.rfl
#align filter.mem_pmap Filter.mem_pmap
-/

#print Filter.Ptendsto /-
/-- Generic "limit of a partial function" predicate. `ptendsto r l₁ l₂` asserts that for every
`l₂`-neighborhood `a`, the `p`-core of `a` is an `l₁`-neighborhood. One generalization of
`filter.tendsto` to partial function. -/
def Ptendsto (f : α →. β) (l₁ : Filter α) (l₂ : Filter β) :=
  l₁.pmap f ≤ l₂
#align filter.ptendsto Filter.Ptendsto
-/

#print Filter.ptendsto_def /-
theorem ptendsto_def (f : α →. β) (l₁ : Filter α) (l₂ : Filter β) :
    Ptendsto f l₁ l₂ ↔ ∀ s ∈ l₂, f.core s ∈ l₁ :=
  Iff.rfl
#align filter.ptendsto_def Filter.ptendsto_def
-/

#print Filter.ptendsto_iff_rtendsto /-
theorem ptendsto_iff_rtendsto (l₁ : Filter α) (l₂ : Filter β) (f : α →. β) :
    Ptendsto f l₁ l₂ ↔ Rtendsto f.graph' l₁ l₂ :=
  Iff.rfl
#align filter.ptendsto_iff_rtendsto Filter.ptendsto_iff_rtendsto
-/

/- warning: filter.pmap_res -> Filter.pmap_res is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (l : Filter.{u1} α) (s : Set.{u1} α) (f : α -> β), Eq.{succ u2} (Filter.{u2} β) (Filter.pmap.{u1, u2} α β (PFun.res.{u1, u2} α β f s) l) (Filter.map.{u1, u2} α β f (Inf.inf.{u1} (Filter.{u1} α) (Filter.hasInf.{u1} α) l (Filter.principal.{u1} α s)))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} (l : Filter.{u1} α) (s : Set.{u1} α) (f : α -> β), Eq.{succ u2} (Filter.{u2} β) (Filter.pmap.{u1, u2} α β (PFun.res.{u1, u2} α β f s) l) (Filter.map.{u1, u2} α β f (Inf.inf.{u1} (Filter.{u1} α) (Filter.instInfFilter.{u1} α) l (Filter.principal.{u1} α s)))
Case conversion may be inaccurate. Consider using '#align filter.pmap_res Filter.pmap_resₓ'. -/
theorem pmap_res (l : Filter α) (s : Set α) (f : α → β) : pmap (PFun.res f s) l = map f (l ⊓ 𝓟 s) :=
  by
  ext t
  simp only [PFun.core_res, mem_pmap, mem_map, mem_inf_principal, imp_iff_not_or]
  rfl
#align filter.pmap_res Filter.pmap_res

/- warning: filter.tendsto_iff_ptendsto -> Filter.tendsto_iff_ptendsto is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (l₁ : Filter.{u1} α) (l₂ : Filter.{u2} β) (s : Set.{u1} α) (f : α -> β), Iff (Filter.Tendsto.{u1, u2} α β f (Inf.inf.{u1} (Filter.{u1} α) (Filter.hasInf.{u1} α) l₁ (Filter.principal.{u1} α s)) l₂) (Filter.Ptendsto.{u1, u2} α β (PFun.res.{u1, u2} α β f s) l₁ l₂)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} (l₁ : Filter.{u1} α) (l₂ : Filter.{u2} β) (s : Set.{u1} α) (f : α -> β), Iff (Filter.Tendsto.{u1, u2} α β f (Inf.inf.{u1} (Filter.{u1} α) (Filter.instInfFilter.{u1} α) l₁ (Filter.principal.{u1} α s)) l₂) (Filter.Ptendsto.{u1, u2} α β (PFun.res.{u1, u2} α β f s) l₁ l₂)
Case conversion may be inaccurate. Consider using '#align filter.tendsto_iff_ptendsto Filter.tendsto_iff_ptendstoₓ'. -/
theorem tendsto_iff_ptendsto (l₁ : Filter α) (l₂ : Filter β) (s : Set α) (f : α → β) :
    Tendsto f (l₁ ⊓ 𝓟 s) l₂ ↔ Ptendsto (PFun.res f s) l₁ l₂ := by
  simp only [tendsto, ptendsto, pmap_res]
#align filter.tendsto_iff_ptendsto Filter.tendsto_iff_ptendsto

#print Filter.tendsto_iff_ptendsto_univ /-
theorem tendsto_iff_ptendsto_univ (l₁ : Filter α) (l₂ : Filter β) (f : α → β) :
    Tendsto f l₁ l₂ ↔ Ptendsto (PFun.res f Set.univ) l₁ l₂ :=
  by
  rw [← tendsto_iff_ptendsto]
  simp [principal_univ]
#align filter.tendsto_iff_ptendsto_univ Filter.tendsto_iff_ptendsto_univ
-/

#print Filter.pcomap' /-
/-- Inverse map of a filter under a partial function. One generalization of `filter.comap` to
partial functions. -/
def pcomap' (f : α →. β) (l : Filter β) : Filter α :=
  Filter.rcomap' f.graph' l
#align filter.pcomap' Filter.pcomap'
-/

#print Filter.Ptendsto' /-
/-- Generic "limit of a partial function" predicate. `ptendsto' r l₁ l₂` asserts that for every
`l₂`-neighborhood `a`, the `p`-preimage of `a` is an `l₁`-neighborhood. One generalization of
`filter.tendsto` to partial functions. -/
def Ptendsto' (f : α →. β) (l₁ : Filter α) (l₂ : Filter β) :=
  l₁ ≤ l₂.rcomap' f.graph'
#align filter.ptendsto' Filter.Ptendsto'
-/

#print Filter.ptendsto'_def /-
theorem ptendsto'_def (f : α →. β) (l₁ : Filter α) (l₂ : Filter β) :
    Ptendsto' f l₁ l₂ ↔ ∀ s ∈ l₂, f.Preimage s ∈ l₁ :=
  rtendsto'_def _ _ _
#align filter.ptendsto'_def Filter.ptendsto'_def
-/

#print Filter.ptendsto_of_ptendsto' /-
theorem ptendsto_of_ptendsto' {f : α →. β} {l₁ : Filter α} {l₂ : Filter β} :
    Ptendsto' f l₁ l₂ → Ptendsto f l₁ l₂ :=
  by
  rw [ptendsto_def, ptendsto'_def]
  exact fun h s sl₂ => mem_of_superset (h s sl₂) (PFun.preimage_subset_core _ _)
#align filter.ptendsto_of_ptendsto' Filter.ptendsto_of_ptendsto'
-/

#print Filter.ptendsto'_of_ptendsto /-
theorem ptendsto'_of_ptendsto {f : α →. β} {l₁ : Filter α} {l₂ : Filter β} (h : f.Dom ∈ l₁) :
    Ptendsto f l₁ l₂ → Ptendsto' f l₁ l₂ :=
  by
  rw [ptendsto_def, ptendsto'_def]
  intro h' s sl₂
  rw [PFun.preimage_eq]
  exact inter_mem (h' s sl₂) h
#align filter.ptendsto'_of_ptendsto Filter.ptendsto'_of_ptendsto
-/

end Filter