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

! This file was ported from Lean 3 source module topology.sheaves.limits
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Topology.Sheaves.Sheaf
import Mathbin.CategoryTheory.Sites.Limits
import Mathbin.CategoryTheory.Limits.FunctorCategory

/-!
# Presheaves in `C` have limits and colimits when `C` does.
-/

noncomputable section

universe v u

open CategoryTheory

open CategoryTheory.Limits

variable {C : Type u} [Category.{v} C] {J : Type v} [SmallCategory J]

namespace TopCat

instance [HasLimits C] (X : TopCat) : HasLimits (Presheaf C X) :=
  Limits.functorCategoryHasLimitsOfSize.{v, v}

instance [HasColimits C] (X : TopCat) : HasColimitsOfSize.{v} (Presheaf C X) :=
  Limits.functorCategoryHasColimitsOfSize

instance [HasLimits C] (X : TopCat) : CreatesLimits (Sheaf.forget C X) :=
  Sheaf.CategoryTheory.SheafToPresheaf.CategoryTheory.createsLimits.{u, v, v}

instance [HasLimits C] (X : TopCat) : HasLimitsOfSize.{v} (Sheaf.{v} C X) :=
  has_limits_of_has_limits_creates_limits (Sheaf.forget C X)

theorem isSheaf_of_isLimit [HasLimits C] {X : TopCat} (F : J ⥤ Presheaf.{v} C X)
    (H : ∀ j, (F.obj j).IsSheaf) {c : Cone F} (hc : IsLimit c) : c.pt.IsSheaf :=
  by
  let F' : J ⥤ sheaf C X :=
    { obj := fun j => ⟨F.obj j, H j⟩
      map := fun X Y f => ⟨F.map f⟩ }
  let e : F' ⋙ sheaf.forget C X ≅ F := nat_iso.of_components (fun _ => iso.refl _) (by tidy)
  exact
    presheaf.is_sheaf_of_iso
      ((is_limit_of_preserves (sheaf.forget C X) (limit.is_limit F')).conePointsIsoOfNatIso hc e)
      (limit F').2
#align Top.is_sheaf_of_is_limit TopCat.isSheaf_of_isLimit

theorem limit_isSheaf [HasLimits C] {X : TopCat} (F : J ⥤ Presheaf.{v} C X)
    (H : ∀ j, (F.obj j).IsSheaf) : (limit F).IsSheaf :=
  isSheaf_of_isLimit F H (limit.isLimit F)
#align Top.limit_is_sheaf TopCat.limit_isSheaf

end TopCat


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

import .basic .cast

namespace tup
variables {α : Type*} {n : ℕ}

definition cons : α → α ^ n → α ^ (n + 1)
| x _ ⟨0, _⟩ := x
| _ xs ⟨i+1, h⟩ := xs[⟨i, nat.lt_of_succ_lt_succ h⟩]

@[simp] lemma ith_cons_zero {x : α} {xs : α ^ n} :
(cons x xs)[⟨0, nat.zero_lt_succ n⟩] = x := rfl

@[simp] lemma ith_cons_succ {x : α} {xs : α ^ n} {i : ℕ} {hi : i + 1 < n + 1} :
(cons x xs)[⟨i+1, hi⟩] = xs[⟨i, nat.lt_of_succ_lt_succ hi⟩] := rfl

@[simp] lemma cons_val_zero {x : α} {xs : α ^ n} :
(cons x xs)[0] = x := rfl

@[simp] lemma cons_val_succ {x : α} {xs : α ^ n} :
∀ (i : fin n), (cons x xs)[fin.succ i] = xs[i] := λ ⟨_,_⟩, rfl

@[reducible] definition head : α ^ (n+1) → α :=
λ xs, xs[0]

@[simp] lemma head_cons {x : α} {xs : α ^ n} :
head (cons x xs) = x := rfl

definition head_of_nonzero (h : n ≠ 0) : α ^ n → α :=
match n, h with
| 0, h := absurd rfl h
| (n+1), _ := head
end

@[reducible] definition tail {n : ℕ} : α ^ (n+1) → α ^ n :=
λ xs i, xs[fin.succ i]

@[simp] lemma ith_tail {n : ℕ} (xs : α ^ (n+1)) :
∀ i, xs.tail[i] = xs[fin.succ i] := λ i, rfl

@[simp] lemma tail_cons {n : ℕ} {x : α} {xs : α ^ n} :
tail (cons x xs) = xs := ext $ λ ⟨_,_⟩, rfl

definition tail_of_nonzero (h : n ≠ 0) : α ^ n → α ^ (n-1) :=
match n, h with
| 0, h := absurd rfl h
| (n+1), _ := tail
end

@[simp] lemma cons_head_tail (xs : α ^ (n+1)) :
cons (head xs) (tail xs) = xs :=
ext $ λ i, match i with
| ⟨0,_⟩ := rfl
| ⟨i+1,_⟩ := rfl
end

section cast
variables {m : ℕ} (hmn : m = n) (hnm : n = m)

@[simp] lemma cons_cast :
∀ {m n : ℕ} (x : α) (xs : α ^ m) (h : m = n),
cons x (cast h xs) = cast (@congr_arg ℕ ℕ m n _ h) (cons x xs)
| n .(n) x xs rfl := by simp

@[simp] lemma cast_cons :
∀ {m n : ℕ} (x : α) (xs : α ^ m) (h : m + 1 = n + 1),
cast h (cons x xs) = cons x (cast (nat.succ_inj h) xs)
| n .(n) x xs rfl := by simp

@[simp] lemma head_cast :
∀ {m n : ℕ} (xs : α ^ (m + 1)) (h : m + 1 = n + 1),
head (cast h xs) = head xs
| n .(n) xs rfl := rfl

@[simp] lemma tail_cast :
∀ {m n : ℕ} (xs : α ^ (m + 1)) (h : m + 1 = n + 1),
tail (cast h xs) = cast (nat.succ_inj h) (tail xs)
| n .(n) xs rfl := by simp

end cast

end tup

notation x :: xs := tup.cons x xs
notation `⟪` l:(foldr `, ` (h t, tup.cons h t) tup.nil `⟫`) := l

section rec
universe u
variables {α : Type u} {C : Π {n : ℕ}, α ^ n → Sort*}
open tup

@[recursor 6]
definition tup.cons_rec :
C nil → (Π (x : α) {n : ℕ} (xs : α ^ n), C xs → C (x :: xs)) → (Π {n : ℕ} (xs : tup.{u} α n), C xs)
| h0 _ 0 xs := eq.rec_on (eq.symm (eq_nil xs)) h0
| h0 hs (n+1) xs := eq.rec_on (cons_head_tail xs) (hs (head xs) (tail xs) (tup.cons_rec h0 hs (tail xs)))

@[elab_as_eliminator]
definition tup.cons_rec_on {n : ℕ} (xs : α ^ n) :
C nil → (Π (x : α) {n : ℕ} (xs : α ^ n), C xs → C (x :: xs)) → C xs :=
assume h0 hs, tup.cons_rec h0 hs xs

end rec


/-
Copyright (c) 2020 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.nat.parity
import Mathlib.data.polynomial.ring_division
import Mathlib.group_theory.order_of_element
import Mathlib.ring_theory.integral_domain
import Mathlib.number_theory.divisors
import Mathlib.data.zmod.basic
import Mathlib.tactic.zify
import Mathlib.field_theory.separable
import Mathlib.field_theory.finite.basic
import Mathlib.PostPort

universes u_1 u_2 u_5 l u_3 u_4 u_7

namespace Mathlib

/-!
# Roots of unity and primitive roots of unity

We define roots of unity in the context of an arbitrary commutative monoid,
as a subgroup of the group of units. We also define a predicate `is_primitive_root` on commutative
monoids, expressing that an element is a primitive root of unity.

## Main definitions

* `roots_of_unity n M`, for `n : ℕ+` is the subgroup of the units of a commutative monoid `M`
  consisting of elements `x` that satisfy `x ^ n = 1`.
* `is_primitive_root ζ k`: an element `ζ` is a primitive `k`-th root of unity if `ζ ^ k = 1`,
  and if `l` satisfies `ζ ^ l = 1` then `k ∣ l`.
* `primitive_roots k R`: the finset of primitive `k`-th roots of unity in an integral domain `R`.

## Main results

* `roots_of_unity.is_cyclic`: the roots of unity in an integral domain form a cyclic group.
* `is_primitive_root.zmod_equiv_gpowers`: `zmod k` is equivalent to
  the subgroup generated by a primitive `k`-th root of unity.
* `is_primitive_root.gpowers_eq`: in an integral domain, the subgroup generated by
  a primitive `k`-th root of unity is equal to the `k`-th roots of unity.
* `is_primitive_root.card_primitive_roots`: if an integral domain
   has a primitive `k`-th root of unity, then it has `φ k` of them.

## Implementation details

It is desirable that `roots_of_unity` is a subgroup,
and it will mainly be applied to rings (e.g. the ring of integers in a number field) and fields.
We therefore implement it as a subgroup of the units of a commutative monoid.

We have chosen to define `roots_of_unity n` for `n : ℕ+`, instead of `n : ℕ`,
because almost all lemmas need the positivity assumption,
and in particular the type class instances for `fintype` and `is_cyclic`.

On the other hand, for primitive roots of unity, it is desirable to have a predicate
not just on units, but directly on elements of the ring/field.
For example, we want to say that `exp (2 * pi * I / n)` is a primitive `n`-th root of unity
in the complex numbers, without having to turn that number into a unit first.

This creates a little bit of friction, but lemmas like `is_primitive_root.is_unit` and
`is_primitive_root.coe_units_iff` should provide the necessary glue.

-/

/-- `roots_of_unity k M` is the subgroup of elements `m : units M` that satisfy `m ^ k = 1` -/
def roots_of_unity (k : ℕ+) (M : Type u_1) [comm_monoid M] : subgroup (units M) :=
  subgroup.mk (set_of fun (ζ : units M) => ζ ^ ↑k = 1) sorry sorry sorry

@[simp] theorem mem_roots_of_unity {M : Type u_1} [comm_monoid M] (k : ℕ+) (ζ : units M) :
    ζ ∈ roots_of_unity k M ↔ ζ ^ ↑k = 1 :=
  iff.rfl

theorem roots_of_unity_le_of_dvd {M : Type u_1} [comm_monoid M] {k : ℕ+} {l : ℕ+} (h : k ∣ l) :
    roots_of_unity k M ≤ roots_of_unity l M :=
  sorry

theorem map_roots_of_unity {M : Type u_1} {N : Type u_2} [comm_monoid M] [comm_monoid N]
    (f : units M →* units N) (k : ℕ+) : subgroup.map f (roots_of_unity k M) ≤ roots_of_unity k N :=
  sorry

theorem mem_roots_of_unity_iff_mem_nth_roots {R : Type u_5} [integral_domain R] {k : ℕ+}
    {ζ : units R} : ζ ∈ roots_of_unity k R ↔ ↑ζ ∈ polynomial.nth_roots (↑k) 1 :=
  sorry

/-- Equivalence between the `k`-th roots of unity in `R` and the `k`-th roots of `1`.

This is implemented as equivalence of subtypes,
because `roots_of_unity` is a subgroup of the group of units,
whereas `nth_roots` is a multiset. -/
def roots_of_unity_equiv_nth_roots (R : Type u_5) [integral_domain R] (k : ℕ+) :
    ↥(roots_of_unity k R) ≃ Subtype fun (x : R) => x ∈ polynomial.nth_roots (↑k) 1 :=
  equiv.mk (fun (x : ↥(roots_of_unity k R)) => { val := ↑x, property := sorry })
    (fun (x : Subtype fun (x : R) => x ∈ polynomial.nth_roots (↑k) 1) =>
      { val := units.mk (↑x) (↑x ^ (↑k - 1)) sorry sorry, property := sorry })
    sorry sorry

@[simp] theorem roots_of_unity_equiv_nth_roots_apply {R : Type u_5} [integral_domain R] {k : ℕ+}
    (x : ↥(roots_of_unity k R)) : ↑(coe_fn (roots_of_unity_equiv_nth_roots R k) x) = ↑x :=
  rfl

@[simp] theorem roots_of_unity_equiv_nth_roots_symm_apply {R : Type u_5} [integral_domain R]
    {k : ℕ+} (x : Subtype fun (x : R) => x ∈ polynomial.nth_roots (↑k) 1) :
    ↑(coe_fn (equiv.symm (roots_of_unity_equiv_nth_roots R k)) x) = ↑x :=
  rfl

protected instance roots_of_unity.fintype (R : Type u_5) [integral_domain R] (k : ℕ+) :
    fintype ↥(roots_of_unity k R) :=
  fintype.of_equiv (Subtype fun (x : R) => x ∈ polynomial.nth_roots (↑k) 1)
    (equiv.symm (roots_of_unity_equiv_nth_roots R k))

protected instance roots_of_unity.is_cyclic (R : Type u_5) [integral_domain R] (k : ℕ+) :
    is_cyclic ↥(roots_of_unity k R) :=
  is_cyclic_of_subgroup_integral_domain
    (monoid_hom.comp (units.coe_hom R) (subgroup.subtype (roots_of_unity k R)))
    (function.injective.comp units.ext subtype.val_injective)

theorem card_roots_of_unity (R : Type u_5) [integral_domain R] (k : ℕ+) :
    fintype.card ↥(roots_of_unity k R) ≤ ↑k :=
  sorry

/-- An element `ζ` is a primitive `k`-th root of unity if `ζ ^ k = 1`,
and if `l` satisfies `ζ ^ l = 1` then `k ∣ l`. -/
structure is_primitive_root {M : Type u_1} [comm_monoid M] (ζ : M) (k : ℕ) where
  pow_eq_one : ζ ^ k = 1
  dvd_of_pow_eq_one : ∀ (l : ℕ), ζ ^ l = 1 → k ∣ l

/-- `primitive_roots k R` is the finset of primitive `k`-th roots of unity
in the integral domain `R`. -/
def primitive_roots (k : ℕ) (R : Type u_1) [integral_domain R] : finset R :=
  finset.filter (fun (ζ : R) => is_primitive_root ζ k)
    (multiset.to_finset (polynomial.nth_roots k 1))

@[simp] theorem mem_primitive_roots {R : Type u_5} [integral_domain R] {k : ℕ} {ζ : R}
    (h0 : 0 < k) : ζ ∈ primitive_roots k R ↔ is_primitive_root ζ k :=
  sorry

namespace is_primitive_root

theorem iff_def {M : Type u_1} [comm_monoid M] (ζ : M) (k : ℕ) :
    is_primitive_root ζ k ↔ ζ ^ k = 1 ∧ ∀ (l : ℕ), ζ ^ l = 1 → k ∣ l :=
  sorry

theorem mk_of_lt {M : Type u_1} [comm_monoid M] {k : ℕ} (ζ : M) (hk : 0 < k) (h1 : ζ ^ k = 1)
    (h : ∀ (l : ℕ), 0 < l → l < k → ζ ^ l ≠ 1) : is_primitive_root ζ k :=
  sorry

theorem pow_eq_one_iff_dvd {M : Type u_1} [comm_monoid M] {k : ℕ} {ζ : M}
    (h : is_primitive_root ζ k) (l : ℕ) : ζ ^ l = 1 ↔ k ∣ l :=
  sorry

theorem is_unit {M : Type u_1} [comm_monoid M] {k : ℕ} {ζ : M} (h : is_primitive_root ζ k)
    (h0 : 0 < k) : is_unit ζ :=
  sorry

theorem pow_ne_one_of_pos_of_lt {M : Type u_1} [comm_monoid M] {k : ℕ} {l : ℕ} {ζ : M}
    (h : is_primitive_root ζ k) (h0 : 0 < l) (hl : l < k) : ζ ^ l ≠ 1 :=
  mt (nat.le_of_dvd h0 ∘ dvd_of_pow_eq_one h l) (not_le_of_lt hl)

theorem pow_inj {M : Type u_1} [comm_monoid M] {k : ℕ} {ζ : M} (h : is_primitive_root ζ k) {i : ℕ}
    {j : ℕ} (hi : i < k) (hj : j < k) (H : ζ ^ i = ζ ^ j) : i = j :=
  sorry

theorem one {M : Type u_1} [comm_monoid M] : is_primitive_root 1 1 :=
  mk (pow_one 1) fun (l : ℕ) (hl : 1 ^ l = 1) => one_dvd l

@[simp] theorem one_right_iff {M : Type u_1} [comm_monoid M] {ζ : M} :
    is_primitive_root ζ 1 ↔ ζ = 1 :=
  sorry

@[simp] theorem coe_units_iff {M : Type u_1} [comm_monoid M] {k : ℕ} {ζ : units M} :
    is_primitive_root (↑ζ) k ↔ is_primitive_root ζ k :=
  sorry

theorem pow_of_coprime {M : Type u_1} [comm_monoid M] {k : ℕ} {ζ : M} (h : is_primitive_root ζ k)
    (i : ℕ) (hi : nat.coprime i k) : is_primitive_root (ζ ^ i) k :=
  sorry

theorem pow_of_prime {M : Type u_1} [comm_monoid M] {k : ℕ} {ζ : M} (h : is_primitive_root ζ k)
    {p : ℕ} (hprime : nat.prime p) (hdiv : ¬p ∣ k) : is_primitive_root (ζ ^ p) k :=
  pow_of_coprime h p (iff.mpr (nat.prime.coprime_iff_not_dvd hprime) hdiv)

theorem pow_iff_coprime {M : Type u_1} [comm_monoid M] {k : ℕ} {ζ : M} (h : is_primitive_root ζ k)
    (h0 : 0 < k) (i : ℕ) : is_primitive_root (ζ ^ i) k ↔ nat.coprime i k :=
  sorry

theorem gpow_eq_one {G : Type u_3} [comm_group G] {k : ℕ} {ζ : G} (h : is_primitive_root ζ k) :
    ζ ^ ↑k = 1 :=
  pow_eq_one h

theorem gpow_eq_one_iff_dvd {G : Type u_3} [comm_group G] {k : ℕ} {ζ : G}
    (h : is_primitive_root ζ k) (l : ℤ) : ζ ^ l = 1 ↔ ↑k ∣ l :=
  sorry

theorem inv {G : Type u_3} [comm_group G] {k : ℕ} {ζ : G} (h : is_primitive_root ζ k) :
    is_primitive_root (ζ⁻¹) k :=
  sorry

@[simp] theorem inv_iff {G : Type u_3} [comm_group G] {k : ℕ} {ζ : G} :
    is_primitive_root (ζ⁻¹) k ↔ is_primitive_root ζ k :=
  sorry

theorem gpow_of_gcd_eq_one {G : Type u_3} [comm_group G] {k : ℕ} {ζ : G} (h : is_primitive_root ζ k)
    (i : ℤ) (hi : int.gcd i ↑k = 1) : is_primitive_root (ζ ^ i) k :=
  sorry

@[simp] theorem coe_subgroup_iff {G : Type u_3} [comm_group G] {k : ℕ} (H : subgroup G) {ζ : ↥H} :
    is_primitive_root (↑ζ) k ↔ is_primitive_root ζ k :=
  sorry

theorem fpow_eq_one {G₀ : Type u_4} [comm_group_with_zero G₀] {k : ℕ} {ζ : G₀}
    (h : is_primitive_root ζ k) : ζ ^ ↑k = 1 :=
  pow_eq_one h

theorem fpow_eq_one_iff_dvd {G₀ : Type u_4} [comm_group_with_zero G₀] {k : ℕ} {ζ : G₀}
    (h : is_primitive_root ζ k) (l : ℤ) : ζ ^ l = 1 ↔ ↑k ∣ l :=
  sorry

theorem inv' {G₀ : Type u_4} [comm_group_with_zero G₀] {k : ℕ} {ζ : G₀}
    (h : is_primitive_root ζ k) : is_primitive_root (ζ⁻¹) k :=
  sorry

@[simp] theorem inv_iff' {G₀ : Type u_4} [comm_group_with_zero G₀] {k : ℕ} {ζ : G₀} :
    is_primitive_root (ζ⁻¹) k ↔ is_primitive_root ζ k :=
  sorry

theorem fpow_of_gcd_eq_one {G₀ : Type u_4} [comm_group_with_zero G₀] {k : ℕ} {ζ : G₀}
    (h : is_primitive_root ζ k) (i : ℤ) (hi : int.gcd i ↑k = 1) : is_primitive_root (ζ ^ i) k :=
  sorry

@[simp] theorem primitive_roots_zero {R : Type u_5} [integral_domain R] : primitive_roots 0 R = ∅ :=
  sorry

@[simp] theorem primitive_roots_one {R : Type u_5} [integral_domain R] :
    primitive_roots 1 R = singleton 1 :=
  sorry

theorem neg_one {R : Type u_5} [integral_domain R] (p : ℕ) [char_p R p] (hp : p ≠ bit0 1) :
    is_primitive_root (-1) (bit0 1) :=
  sorry

theorem eq_neg_one_of_two_right {R : Type u_5} [integral_domain R] {ζ : R}
    (h : is_primitive_root ζ (bit0 1)) : ζ = -1 :=
  sorry

protected theorem mem_roots_of_unity {R : Type u_5} [integral_domain R] {ζ : units R} {n : ℕ+}
    (h : is_primitive_root ζ ↑n) : ζ ∈ roots_of_unity n R :=
  pow_eq_one h

/-- The (additive) monoid equivalence between `zmod k`
and the powers of a primitive root of unity `ζ`. -/
def zmod_equiv_gpowers {R : Type u_5} [integral_domain R] {k : ℕ} {ζ : units R}
    (h : is_primitive_root ζ k) : zmod k ≃+ additive ↥(subgroup.gpowers ζ) :=
  add_equiv.of_bijective
    (add_monoid_hom.lift_of_surjective (int.cast_add_hom (zmod k)) zmod.int_cast_surjective
      (add_monoid_hom.mk (fun (i : ℤ) => coe_fn additive.of_mul { val := ζ ^ i, property := sorry })
        sorry sorry)
      sorry)
    sorry

@[simp] theorem zmod_equiv_gpowers_apply_coe_int {R : Type u_5} [integral_domain R] {k : ℕ}
    {ζ : units R} (h : is_primitive_root ζ k) (i : ℤ) :
    coe_fn (zmod_equiv_gpowers h) ↑i =
        coe_fn additive.of_mul { val := ζ ^ i, property := Exists.intro i rfl } :=
  sorry

@[simp] theorem zmod_equiv_gpowers_apply_coe_nat {R : Type u_5} [integral_domain R] {k : ℕ}
    {ζ : units R} (h : is_primitive_root ζ k) (i : ℕ) :
    coe_fn (zmod_equiv_gpowers h) ↑i =
        coe_fn additive.of_mul { val := ζ ^ i, property := Exists.intro (↑i) rfl } :=
  sorry

@[simp] theorem zmod_equiv_gpowers_symm_apply_gpow {R : Type u_5} [integral_domain R] {k : ℕ}
    {ζ : units R} (h : is_primitive_root ζ k) (i : ℤ) :
    coe_fn (add_equiv.symm (zmod_equiv_gpowers h))
          (coe_fn additive.of_mul { val := ζ ^ i, property := Exists.intro i rfl }) =
        ↑i :=
  sorry

@[simp] theorem zmod_equiv_gpowers_symm_apply_gpow' {R : Type u_5} [integral_domain R] {k : ℕ}
    {ζ : units R} (h : is_primitive_root ζ k) (i : ℤ) :
    coe_fn (add_equiv.symm (zmod_equiv_gpowers h))
          { val := ζ ^ i, property := Exists.intro i rfl } =
        ↑i :=
  zmod_equiv_gpowers_symm_apply_gpow h i

@[simp] theorem zmod_equiv_gpowers_symm_apply_pow {R : Type u_5} [integral_domain R] {k : ℕ}
    {ζ : units R} (h : is_primitive_root ζ k) (i : ℕ) :
    coe_fn (add_equiv.symm (zmod_equiv_gpowers h))
          (coe_fn additive.of_mul { val := ζ ^ i, property := Exists.intro (↑i) rfl }) =
        ↑i :=
  sorry

@[simp] theorem zmod_equiv_gpowers_symm_apply_pow' {R : Type u_5} [integral_domain R] {k : ℕ}
    {ζ : units R} (h : is_primitive_root ζ k) (i : ℕ) :
    coe_fn (add_equiv.symm (zmod_equiv_gpowers h))
          { val := ζ ^ i, property := Exists.intro (↑i) rfl } =
        ↑i :=
  zmod_equiv_gpowers_symm_apply_pow h i

theorem gpowers_eq {R : Type u_5} [integral_domain R] {k : ℕ+} {ζ : units R}
    (h : is_primitive_root ζ ↑k) : subgroup.gpowers ζ = roots_of_unity k R :=
  sorry

theorem eq_pow_of_mem_roots_of_unity {R : Type u_5} [integral_domain R] {k : ℕ+} {ζ : units R}
    {ξ : units R} (h : is_primitive_root ζ ↑k) (hξ : ξ ∈ roots_of_unity k R) :
    ∃ (i : ℕ), ∃ (hi : i < ↑k), ζ ^ i = ξ :=
  sorry

theorem eq_pow_of_pow_eq_one {R : Type u_5} [integral_domain R] {k : ℕ} {ζ : R} {ξ : R}
    (h : is_primitive_root ζ k) (hξ : ξ ^ k = 1) (h0 : 0 < k) :
    ∃ (i : ℕ), ∃ (H : i < k), ζ ^ i = ξ :=
  sorry

theorem is_primitive_root_iff' {R : Type u_5} [integral_domain R] {k : ℕ+} {ζ : units R}
    {ξ : units R} (h : is_primitive_root ζ ↑k) :
    is_primitive_root ξ ↑k ↔ ∃ (i : ℕ), ∃ (H : i < ↑k), ∃ (hi : nat.coprime i ↑k), ζ ^ i = ξ :=
  sorry

theorem is_primitive_root_iff {R : Type u_5} [integral_domain R] {k : ℕ} {ζ : R} {ξ : R}
    (h : is_primitive_root ζ k) (h0 : 0 < k) :
    is_primitive_root ξ k ↔ ∃ (i : ℕ), ∃ (H : i < k), ∃ (hi : nat.coprime i k), ζ ^ i = ξ :=
  sorry

theorem card_roots_of_unity' {R : Type u_5} [integral_domain R] {ζ : units R} {n : ℕ+}
    (h : is_primitive_root ζ ↑n) : fintype.card ↥(roots_of_unity n R) = ↑n :=
  sorry

theorem card_roots_of_unity {R : Type u_5} [integral_domain R] {ζ : R} {n : ℕ+}
    (h : is_primitive_root ζ ↑n) : fintype.card ↥(roots_of_unity n R) = ↑n :=
  sorry

/-- The cardinality of the multiset `nth_roots ↑n (1 : R)` is `n`
if there is a primitive root of unity in `R`. -/
theorem card_nth_roots {R : Type u_5} [integral_domain R] {ζ : R} {n : ℕ}
    (h : is_primitive_root ζ n) : coe_fn multiset.card (polynomial.nth_roots n 1) = n :=
  sorry

/-- The multiset `nth_roots ↑n (1 : R)` has no repeated elements
if there is a primitive root of unity in `R`. -/
theorem nth_roots_nodup {R : Type u_5} [integral_domain R] {ζ : R} {n : ℕ}
    (h : is_primitive_root ζ n) : multiset.nodup (polynomial.nth_roots n 1) :=
  sorry

@[simp] theorem card_nth_roots_finset {R : Type u_5} [integral_domain R] {ζ : R} {n : ℕ}
    (h : is_primitive_root ζ n) : finset.card (polynomial.nth_roots_finset n R) = n :=
  sorry

/-- If an integral domain has a primitive `k`-th root of unity, then it has `φ k` of them. -/
theorem card_primitive_roots {R : Type u_5} [integral_domain R] {ζ : R} {k : ℕ}
    (h : is_primitive_root ζ k) (h0 : 0 < k) : finset.card (primitive_roots k R) = nat.totient k :=
  sorry

/-- The sets `primitive_roots k R` are pairwise disjoint. -/
theorem disjoint {R : Type u_5} [integral_domain R] {k : ℕ} {l : ℕ} (hk : 0 < k) (hl : 0 < l)
    (h : k ≠ l) : disjoint (primitive_roots k R) (primitive_roots l R) :=
  sorry

/-- If there is a `n`-th primitive root of unity in `R` and `b` divides `n`,
then there is a `b`-th primitive root of unity in `R`. -/
theorem pow {R : Type u_5} [integral_domain R] {ζ : R} {n : ℕ} {a : ℕ} {b : ℕ} (hn : 0 < n)
    (h : is_primitive_root ζ n) (hprod : n = a * b) : is_primitive_root (ζ ^ a) b :=
  sorry

/-- `nth_roots n` as a `finset` is equal to the union of `primitive_roots i R` for `i ∣ n`
if there is a primitive root of unity in `R`. -/
theorem nth_roots_one_eq_bUnion_primitive_roots' {R : Type u_5} [integral_domain R] {ζ : R} {n : ℕ+}
    (h : is_primitive_root ζ ↑n) :
    polynomial.nth_roots_finset (↑n) R =
        finset.bUnion (nat.divisors ↑n) fun (i : ℕ) => primitive_roots i R :=
  sorry

/-- `nth_roots n` as a `finset` is equal to the union of `primitive_roots i R` for `i ∣ n`
if there is a primitive root of unity in `R`. -/
theorem nth_roots_one_eq_bUnion_primitive_roots {R : Type u_5} [integral_domain R] {ζ : R} {n : ℕ}
    (hpos : 0 < n) (h : is_primitive_root ζ n) :
    polynomial.nth_roots_finset n R =
        finset.bUnion (nat.divisors n) fun (i : ℕ) => primitive_roots i R :=
  nth_roots_one_eq_bUnion_primitive_roots' h

/--`μ` is integral over `ℤ`. -/
theorem is_integral {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) : is_integral ℤ μ :=
  sorry

/--The minimal polynomial of a root of unity `μ` divides `X ^ n - 1`. -/
theorem minpoly_dvd_X_pow_sub_one {n : ℕ} {K : Type u_7} [field K] {μ : K}
    (h : is_primitive_root μ n) (hpos : 0 < n) [char_zero K] : minpoly ℤ μ ∣ polynomial.X ^ n - 1 :=
  sorry

/-- The reduction modulo `p` of the minimal polynomial of a root of unity `μ` is separable. -/
theorem separable_minpoly_mod {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {p : ℕ} [fact (nat.prime p)] (hdiv : ¬p ∣ n) :
    polynomial.separable (polynomial.map (int.cast_ring_hom (zmod p)) (minpoly ℤ μ)) :=
  sorry

/-- The reduction modulo `p` of the minimal polynomial of a root of unity `μ` is squarefree. -/
theorem squarefree_minpoly_mod {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {p : ℕ} [fact (nat.prime p)] (hdiv : ¬p ∣ n) :
    squarefree (polynomial.map (int.cast_ring_hom (zmod p)) (minpoly ℤ μ)) :=
  polynomial.separable.squarefree (separable_minpoly_mod h hpos hdiv)

/- Let `P` be the minimal polynomial of a root of unity `μ` and `Q` be the minimal polynomial of
`μ ^ p`, where `p` is a prime that does not divide `n`. Then `P` divides `expand ℤ p Q`. -/

theorem minpoly_dvd_expand {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {p : ℕ} (hprime : nat.prime p) (hdiv : ¬p ∣ n) :
    minpoly ℤ μ ∣ coe_fn (polynomial.expand ℤ p) (minpoly ℤ (μ ^ p)) :=
  sorry

/- Let `P` be the minimal polynomial of a root of unity `μ` and `Q` be the minimal polynomial of
`μ ^ p`, where `p` is a prime that does not divide `n`. Then `P` divides `Q ^ p` modulo `p`. -/

theorem minpoly_dvd_pow_mod {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {p : ℕ} [hprime : fact (nat.prime p)] (hdiv : ¬p ∣ n) :
    polynomial.map (int.cast_ring_hom (zmod p)) (minpoly ℤ μ) ∣
        polynomial.map (int.cast_ring_hom (zmod p)) (minpoly ℤ (μ ^ p)) ^ p :=
  sorry

/- Let `P` be the minimal polynomial of a root of unity `μ` and `Q` be the minimal polynomial of
`μ ^ p`, where `p` is a prime that does not divide `n`. Then `P` divides `Q` modulo `p`. -/

theorem minpoly_dvd_mod_p {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {p : ℕ} [hprime : fact (nat.prime p)] (hdiv : ¬p ∣ n) :
    polynomial.map (int.cast_ring_hom (zmod p)) (minpoly ℤ μ) ∣
        polynomial.map (int.cast_ring_hom (zmod p)) (minpoly ℤ (μ ^ p)) :=
  sorry

/-- If `p` is a prime that does not divide `n`,
then the minimal polynomials of a primitive `n`-th root of unity `μ`
and of `μ ^ p` are the same. -/
theorem minpoly_eq_pow {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {p : ℕ} [hprime : fact (nat.prime p)] (hdiv : ¬p ∣ n) :
    minpoly ℤ μ = minpoly ℤ (μ ^ p) :=
  sorry

/-- If `m : ℕ` is coprime with `n`,
then the minimal polynomials of a primitive `n`-th root of unity `μ`
and of `μ ^ m` are the same. -/
theorem minpoly_eq_pow_coprime {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {m : ℕ} (hcop : nat.coprime m n) :
    minpoly ℤ μ = minpoly ℤ (μ ^ m) :=
  sorry

/-- If `m : ℕ` is coprime with `n`,
then the minimal polynomial of a primitive `n`-th root of unity `μ`
has `μ ^ m` as root. -/
theorem pow_is_root_minpoly {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] {m : ℕ} (hcop : nat.coprime m n) :
    polynomial.is_root (polynomial.map (int.cast_ring_hom K) (minpoly ℤ μ)) (μ ^ m) :=
  sorry

/-- `primitive_roots n K` is a subset of the roots of the minimal polynomial of a primitive
`n`-th root of unity `μ`. -/
theorem is_roots_of_minpoly {n : ℕ} {K : Type u_7} [field K] {μ : K} (h : is_primitive_root μ n)
    (hpos : 0 < n) [char_zero K] :
    primitive_roots n K ⊆
        multiset.to_finset
          (polynomial.roots (polynomial.map (int.cast_ring_hom K) (minpoly ℤ μ))) :=
  sorry

/-- The degree of the minimal polynomial of `μ` is at least `totient n`. -/
theorem totient_le_degree_minpoly {n : ℕ} {K : Type u_7} [field K] {μ : K}
    (h : is_primitive_root μ n) (hpos : 0 < n) [char_zero K] :
    nat.totient n ≤ polynomial.nat_degree (minpoly ℤ μ) :=
  sorry

end Mathlib


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

! This file was ported from Lean 3 source module ring_theory.valuation.integers
! leanprover-community/mathlib commit 7b7da89322fe46a16bf03eeb345b0acfc73fe10e
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.RingTheory.Valuation.Basic

/-!
# Ring of integers under a given valuation

The elements with valuation less than or equal to 1.

TODO: Define characteristic predicate.
-/

universe u v w

namespace Valuation

section Ring

variable {R : Type u} {Γ₀ : Type v} [Ring R] [LinearOrderedCommGroupWithZero Γ₀]

variable (v : Valuation R Γ₀)

/-- The ring of integers under a given valuation is the subring of elements with valuation ≤ 1. -/
def integer : Subring R where
  carrier := { x | v x ≤ 1 }
  one_mem' := le_of_eq v.map_one
  mul_mem' {x y} hx hy := by simp only [Set.mem_setOf_eq, _root_.map_mul, mul_le_one' hx hy]
  zero_mem' := by simp only [Set.mem_setOf_eq, _root_.map_zero, zero_le']
  add_mem' {x y} hx hy := le_trans (v.map_add x y) (max_le hx hy)
  neg_mem' {x} hx :=by simp only [Set.mem_setOf_eq] at hx; simpa only [Set.mem_setOf_eq, map_neg]
#align valuation.integer Valuation.integer

end Ring

section CommRing

variable {R : Type u} {Γ₀ : Type v} [CommRing R] [LinearOrderedCommGroupWithZero Γ₀]

variable (v : Valuation R Γ₀)

variable (O : Type w) [CommRing O] [Algebra O R]

/-- Given a valuation v : R → Γ₀ and a ring homomorphism O →+* R, we say that O is the integers of v
if f is injective, and its range is exactly `v.integer`. -/
structure Integers : Prop where
  hom_inj : Function.Injective (algebraMap O R)
  map_le_one : ∀ x, v (algebraMap O R x) ≤ 1
  exists_of_le_one : ∀ ⦃r⦄, v r ≤ 1 → ∃ x, algebraMap O R x = r
#align valuation.integers Valuation.Integers

-- typeclass shortcut
instance : Algebra v.integer R :=
  Algebra.ofSubring v.integer

theorem integer.integers : v.Integers v.integer :=
  { hom_inj := Subtype.coe_injective
    map_le_one := fun r => r.2
    exists_of_le_one := fun r hr => ⟨⟨r, hr⟩, rfl⟩ }
#align valuation.integer.integers Valuation.integer.integers

namespace Integers

variable {v O} [CommRing O] [Algebra O R] (hv : Integers v O)

theorem one_of_isUnit {x : O} (hx : IsUnit x) : v (algebraMap O R x) = 1 :=
  let ⟨u, hu⟩ := hx
  le_antisymm (hv.2 _) <|
    by
    rw [← v.map_one, ← (algebraMap O R).map_one, ← u.mul_inv, ← mul_one (v (algebraMap O R x)), hu,
      (algebraMap O R).map_mul, v.map_mul]
    exact mul_le_mul_left' (hv.2 (u⁻¹ : Units O)) _
#align valuation.integers.one_of_is_unit Valuation.Integers.one_of_isUnit

theorem isUnit_of_one {x : O} (hx : IsUnit (algebraMap O R x)) (hvx : v (algebraMap O R x) = 1) :
    IsUnit x :=
  let ⟨u, hu⟩ := hx
  have h1 : v u ≤ 1 := hu.symm ▸ hv.2 x
  have h2 : v (u⁻¹ : Rˣ) ≤ 1 := by
    rw [← one_mul (v _), ← hvx, ← v.map_mul, ← hu, u.mul_inv, hu, hvx, v.map_one]
  let ⟨r1, hr1⟩ := hv.3 h1
  let ⟨r2, hr2⟩ := hv.3 h2
  ⟨⟨r1, r2, hv.1 <| by rw [RingHom.map_mul, RingHom.map_one, hr1, hr2, Units.mul_inv],
      hv.1 <| by rw [RingHom.map_mul, RingHom.map_one, hr1, hr2, Units.inv_mul]⟩,
    hv.1 <| hr1.trans hu⟩
#align valuation.integers.is_unit_of_one Valuation.Integers.isUnit_of_one

theorem le_of_dvd {x y : O} (h : x ∣ y) : v (algebraMap O R y) ≤ v (algebraMap O R x) := by
  let ⟨z, hz⟩ := h
  rw [← mul_one (v (algebraMap O R x)), hz, RingHom.map_mul, v.map_mul]
  exact mul_le_mul_left' (hv.2 z) _
#align valuation.integers.le_of_dvd Valuation.Integers.le_of_dvd

end Integers

end CommRing

section Field

variable {F : Type u} {Γ₀ : Type v} [Field F] [LinearOrderedCommGroupWithZero Γ₀]

variable {v : Valuation F Γ₀} {O : Type w} [CommRing O] [Algebra O F] (hv : Integers v O)

namespace Integers

theorem dvd_of_le {x y : O} (h : v (algebraMap O F x) ≤ v (algebraMap O F y)) : y ∣ x :=
  by_cases
    (fun hy : algebraMap O F y = 0 =>
      have hx : x = 0 :=
        hv.1 <|
          (algebraMap O F).map_zero.symm ▸ (v.zero_iff.1 <| le_zero_iff.1 (v.map_zero ▸ hy ▸ h))
      hx.symm ▸ dvd_zero y)
    fun hy : algebraMap O F y ≠ 0 =>
    have : v ((algebraMap O F y)⁻¹ * algebraMap O F x) ≤ 1 :=
      by
      rw [← v.map_one, ← inv_mul_cancel hy, v.map_mul, v.map_mul]
      exact mul_le_mul_left' h _
    let ⟨z, hz⟩ := hv.3 this
    ⟨z, hv.1 <| ((algebraMap O F).map_mul y z).symm ▸ hz.symm ▸ (mul_inv_cancel_left₀ hy _).symm⟩
#align valuation.integers.dvd_of_le Valuation.Integers.dvd_of_le

theorem dvd_iff_le {x y : O} : x ∣ y ↔ v (algebraMap O F y) ≤ v (algebraMap O F x) :=
  ⟨hv.le_of_dvd, hv.dvd_of_le⟩
#align valuation.integers.dvd_iff_le Valuation.Integers.dvd_iff_le

theorem le_iff_dvd {x y : O} : v (algebraMap O F x) ≤ v (algebraMap O F y) ↔ y ∣ x :=
  ⟨hv.dvd_of_le, hv.le_of_dvd⟩
#align valuation.integers.le_iff_dvd Valuation.Integers.le_iff_dvd

end Integers

end Field

end Valuation


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Nicolò Cavalleri

! This file was ported from Lean 3 source module topology.continuous_function.algebra
! leanprover-community/mathlib commit 6efec6bb9fcaed3cf1baaddb2eaadd8a2a06679c
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Algebra.Pi
import Mathbin.Algebra.Periodic
import Mathbin.Algebra.Algebra.Subalgebra.Basic
import Mathbin.Algebra.Star.StarAlgHom
import Mathbin.Tactic.FieldSimp
import Mathbin.Topology.Algebra.Module.Basic
import Mathbin.Topology.Algebra.InfiniteSum.Basic
import Mathbin.Topology.Algebra.Star
import Mathbin.Topology.Algebra.UniformGroup
import Mathbin.Topology.ContinuousFunction.Ordered
import Mathbin.Topology.UniformSpace.CompactConvergence

/-!
# Algebraic structures over continuous functions

In this file we define instances of algebraic structures over the type `continuous_map α β`
(denoted `C(α, β)`) of **bundled** continuous maps from `α` to `β`. For example, `C(α, β)`
is a group when `β` is a group, a ring when `β` is a ring, etc.

For each type of algebraic structure, we also define an appropriate subobject of `α → β`
with carrier `{ f : α → β | continuous f }`. For example, when `β` is a group, a subgroup
`continuous_subgroup α β` of `α → β` is constructed with carrier `{ f : α → β | continuous f }`.

Note that, rather than using the derived algebraic structures on these subobjects
(for example, when `β` is a group, the derived group structure on `continuous_subgroup α β`),
one should use `C(α, β)` with the appropriate instance of the structure.
-/

attribute [local elab_without_expected_type] Continuous.comp

namespace ContinuousFunctions

variable {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β]

variable {f g : { f : α → β | Continuous f }}

instance : CoeFun { f : α → β | Continuous f } fun _ => α → β :=
  ⟨Subtype.val⟩

end ContinuousFunctions

namespace ContinuousMap

variable {α : Type _} {β : Type _} {γ : Type _}

variable [TopologicalSpace α] [TopologicalSpace β] [TopologicalSpace γ]

-- ### "mul" and "add"
@[to_additive]
instance hasMul [Mul β] [ContinuousMul β] : Mul C(α, β) :=
  ⟨fun f g => ⟨f * g, continuous_mul.comp (f.Continuous.prod_mk g.Continuous : _)⟩⟩
#align continuous_map.has_mul ContinuousMap.hasMul
#align continuous_map.has_add ContinuousMap.hasAdd

@[simp, norm_cast, to_additive]
theorem coe_mul [Mul β] [ContinuousMul β] (f g : C(α, β)) : ⇑(f * g) = f * g :=
  rfl
#align continuous_map.coe_mul ContinuousMap.coe_mul
#align continuous_map.coe_add ContinuousMap.coe_add

@[simp, to_additive]
theorem mul_apply [Mul β] [ContinuousMul β] (f g : C(α, β)) (x : α) : (f * g) x = f x * g x :=
  rfl
#align continuous_map.mul_apply ContinuousMap.mul_apply
#align continuous_map.add_apply ContinuousMap.add_apply

@[simp, to_additive]
theorem mul_comp [Mul γ] [ContinuousMul γ] (f₁ f₂ : C(β, γ)) (g : C(α, β)) :
    (f₁ * f₂).comp g = f₁.comp g * f₂.comp g :=
  rfl
#align continuous_map.mul_comp ContinuousMap.mul_comp
#align continuous_map.add_comp ContinuousMap.add_comp

-- ### "one"
@[to_additive]
instance [One β] : One C(α, β) :=
  ⟨const α 1⟩

@[simp, norm_cast, to_additive]
theorem coe_one [One β] : ⇑(1 : C(α, β)) = 1 :=
  rfl
#align continuous_map.coe_one ContinuousMap.coe_one
#align continuous_map.coe_zero ContinuousMap.coe_zero

@[simp, to_additive]
theorem one_apply [One β] (x : α) : (1 : C(α, β)) x = 1 :=
  rfl
#align continuous_map.one_apply ContinuousMap.one_apply
#align continuous_map.zero_apply ContinuousMap.zero_apply

@[simp, to_additive]
theorem one_comp [One γ] (g : C(α, β)) : (1 : C(β, γ)).comp g = 1 :=
  rfl
#align continuous_map.one_comp ContinuousMap.one_comp
#align continuous_map.zero_comp ContinuousMap.zero_comp

-- ### "nat_cast"
instance [NatCast β] : NatCast C(α, β) :=
  ⟨fun n => ContinuousMap.const _ n⟩

@[simp, norm_cast]
theorem coe_nat_cast [NatCast β] (n : ℕ) : ((n : C(α, β)) : α → β) = n :=
  rfl
#align continuous_map.coe_nat_cast ContinuousMap.coe_nat_cast

@[simp]
theorem nat_cast_apply [NatCast β] (n : ℕ) (x : α) : (n : C(α, β)) x = n :=
  rfl
#align continuous_map.nat_cast_apply ContinuousMap.nat_cast_apply

-- ### "int_cast"
instance [IntCast β] : IntCast C(α, β) :=
  ⟨fun n => ContinuousMap.const _ n⟩

@[simp, norm_cast]
theorem coe_int_cast [IntCast β] (n : ℤ) : ((n : C(α, β)) : α → β) = n :=
  rfl
#align continuous_map.coe_int_cast ContinuousMap.coe_int_cast

@[simp]
theorem int_cast_apply [IntCast β] (n : ℤ) (x : α) : (n : C(α, β)) x = n :=
  rfl
#align continuous_map.int_cast_apply ContinuousMap.int_cast_apply

-- ### "nsmul" and "pow"
instance hasNsmul [AddMonoid β] [ContinuousAdd β] : SMul ℕ C(α, β) :=
  ⟨fun n f => ⟨n • f, f.Continuous.nsmul n⟩⟩
#align continuous_map.has_nsmul ContinuousMap.hasNsmul

@[to_additive]
instance hasPow [Monoid β] [ContinuousMul β] : Pow C(α, β) ℕ :=
  ⟨fun f n => ⟨f ^ n, f.Continuous.pow n⟩⟩
#align continuous_map.has_pow ContinuousMap.hasPow
#align continuous_map.has_nsmul ContinuousMap.hasNsmul

@[norm_cast, to_additive]
theorem coe_pow [Monoid β] [ContinuousMul β] (f : C(α, β)) (n : ℕ) : ⇑(f ^ n) = f ^ n :=
  rfl
#align continuous_map.coe_pow ContinuousMap.coe_pow
#align continuous_map.coe_nsmul ContinuousMap.coe_nsmul

@[to_additive]
theorem pow_apply [Monoid β] [ContinuousMul β] (f : C(α, β)) (n : ℕ) (x : α) :
    (f ^ n) x = f x ^ n :=
  rfl
#align continuous_map.pow_apply ContinuousMap.pow_apply
#align continuous_map.nsmul_apply ContinuousMap.nsmul_apply

-- don't make auto-generated `coe_nsmul` and `nsmul_apply` simp, as the linter complains they're
-- redundant WRT `coe_smul`
attribute [simp] coe_pow pow_apply

@[to_additive]
theorem pow_comp [Monoid γ] [ContinuousMul γ] (f : C(β, γ)) (n : ℕ) (g : C(α, β)) :
    (f ^ n).comp g = f.comp g ^ n :=
  rfl
#align continuous_map.pow_comp ContinuousMap.pow_comp
#align continuous_map.nsmul_comp ContinuousMap.nsmul_comp

-- don't make `nsmul_comp` simp as the linter complains it's redundant WRT `smul_comp`
attribute [simp] pow_comp

-- ### "inv" and "neg"
@[to_additive]
instance [Group β] [TopologicalGroup β] : Inv C(α, β) where inv f := ⟨f⁻¹, f.Continuous.inv⟩

@[simp, norm_cast, to_additive]
theorem coe_inv [Group β] [TopologicalGroup β] (f : C(α, β)) : ⇑f⁻¹ = f⁻¹ :=
  rfl
#align continuous_map.coe_inv ContinuousMap.coe_inv
#align continuous_map.coe_neg ContinuousMap.coe_neg

@[simp, to_additive]
theorem inv_apply [Group β] [TopologicalGroup β] (f : C(α, β)) (x : α) : f⁻¹ x = (f x)⁻¹ :=
  rfl
#align continuous_map.inv_apply ContinuousMap.inv_apply
#align continuous_map.neg_apply ContinuousMap.neg_apply

@[simp, to_additive]
theorem inv_comp [Group γ] [TopologicalGroup γ] (f : C(β, γ)) (g : C(α, β)) :
    f⁻¹.comp g = (f.comp g)⁻¹ :=
  rfl
#align continuous_map.inv_comp ContinuousMap.inv_comp
#align continuous_map.neg_comp ContinuousMap.neg_comp

-- ### "div" and "sub"
@[to_additive]
instance [Div β] [ContinuousDiv β] : Div C(α, β)
    where div f g := ⟨f / g, f.Continuous.div' g.Continuous⟩

@[simp, norm_cast, to_additive]
theorem coe_div [Div β] [ContinuousDiv β] (f g : C(α, β)) : ⇑(f / g) = f / g :=
  rfl
#align continuous_map.coe_div ContinuousMap.coe_div
#align continuous_map.coe_sub ContinuousMap.coe_sub

@[simp, to_additive]
theorem div_apply [Div β] [ContinuousDiv β] (f g : C(α, β)) (x : α) : (f / g) x = f x / g x :=
  rfl
#align continuous_map.div_apply ContinuousMap.div_apply
#align continuous_map.sub_apply ContinuousMap.sub_apply

@[simp, to_additive]
theorem div_comp [Div γ] [ContinuousDiv γ] (f g : C(β, γ)) (h : C(α, β)) :
    (f / g).comp h = f.comp h / g.comp h :=
  rfl
#align continuous_map.div_comp ContinuousMap.div_comp
#align continuous_map.sub_comp ContinuousMap.sub_comp

-- ### "zpow" and "zsmul"
instance hasZsmul [AddGroup β] [TopologicalAddGroup β] : SMul ℤ C(α, β)
    where smul z f := ⟨z • f, f.Continuous.zsmul z⟩
#align continuous_map.has_zsmul ContinuousMap.hasZsmul

@[to_additive]
instance hasZpow [Group β] [TopologicalGroup β] : Pow C(α, β) ℤ
    where pow f z := ⟨f ^ z, f.Continuous.zpow z⟩
#align continuous_map.has_zpow ContinuousMap.hasZpow
#align continuous_map.has_zsmul ContinuousMap.hasZsmul

@[norm_cast, to_additive]
theorem coe_zpow [Group β] [TopologicalGroup β] (f : C(α, β)) (z : ℤ) : ⇑(f ^ z) = f ^ z :=
  rfl
#align continuous_map.coe_zpow ContinuousMap.coe_zpow
#align continuous_map.coe_zsmul ContinuousMap.coe_zsmul

@[to_additive]
theorem zpow_apply [Group β] [TopologicalGroup β] (f : C(α, β)) (z : ℤ) (x : α) :
    (f ^ z) x = f x ^ z :=
  rfl
#align continuous_map.zpow_apply ContinuousMap.zpow_apply
#align continuous_map.zsmul_apply ContinuousMap.zsmul_apply

-- don't make auto-generated `coe_zsmul` and `zsmul_apply` simp as the linter complains they're
-- redundant WRT `coe_smul`
attribute [simp] coe_zpow zpow_apply

@[to_additive]
theorem zpow_comp [Group γ] [TopologicalGroup γ] (f : C(β, γ)) (z : ℤ) (g : C(α, β)) :
    (f ^ z).comp g = f.comp g ^ z :=
  rfl
#align continuous_map.zpow_comp ContinuousMap.zpow_comp
#align continuous_map.zsmul_comp ContinuousMap.zsmul_comp

-- don't make `zsmul_comp` simp as the linter complains it's redundant WRT `smul_comp`
attribute [simp] zpow_comp

end ContinuousMap

section GroupStructure

/-!
### Group stucture

In this section we show that continuous functions valued in a topological group inherit
the structure of a group.
-/

section Subtype

/-- The `submonoid` of continuous maps `α → β`. -/
@[to_additive "The `add_submonoid` of continuous maps `α → β`. "]
def continuousSubmonoid (α : Type _) (β : Type _) [TopologicalSpace α] [TopologicalSpace β]
    [Monoid β] [ContinuousMul β] : Submonoid (α → β)
    where
  carrier := { f : α → β | Continuous f }
  one_mem' := @continuous_const _ _ _ _ 1
  mul_mem' f g fc gc := fc.mul gc
#align continuous_submonoid continuousSubmonoid
#align continuous_add_submonoid continuousAddSubmonoid

/-- The subgroup of continuous maps `α → β`. -/
@[to_additive "The `add_subgroup` of continuous maps `α → β`. "]
def continuousSubgroup (α : Type _) (β : Type _) [TopologicalSpace α] [TopologicalSpace β] [Group β]
    [TopologicalGroup β] : Subgroup (α → β) :=
  { continuousSubmonoid α β with inv_mem' := fun f fc => Continuous.inv fc }
#align continuous_subgroup continuousSubgroup
#align continuous_add_subgroup continuousAddSubgroup

end Subtype

namespace ContinuousMap

variable {α β : Type _} [TopologicalSpace α] [TopologicalSpace β]

@[to_additive]
instance [Semigroup β] [ContinuousMul β] : Semigroup C(α, β) :=
  coe_injective.Semigroup _ coe_mul

@[to_additive]
instance [CommSemigroup β] [ContinuousMul β] : CommSemigroup C(α, β) :=
  coe_injective.CommSemigroup _ coe_mul

@[to_additive]
instance [MulOneClass β] [ContinuousMul β] : MulOneClass C(α, β) :=
  coe_injective.MulOneClass _ coe_one coe_mul

instance [MulZeroClass β] [ContinuousMul β] : MulZeroClass C(α, β) :=
  coe_injective.MulZeroClass _ coe_zero coe_mul

instance [SemigroupWithZero β] [ContinuousMul β] : SemigroupWithZero C(α, β) :=
  coe_injective.SemigroupWithZero _ coe_zero coe_mul

@[to_additive]
instance [Monoid β] [ContinuousMul β] : Monoid C(α, β) :=
  coe_injective.Monoid _ coe_one coe_mul coe_pow

instance [MonoidWithZero β] [ContinuousMul β] : MonoidWithZero C(α, β) :=
  coe_injective.MonoidWithZero _ coe_zero coe_one coe_mul coe_pow

@[to_additive]
instance [CommMonoid β] [ContinuousMul β] : CommMonoid C(α, β) :=
  coe_injective.CommMonoid _ coe_one coe_mul coe_pow

instance [CommMonoidWithZero β] [ContinuousMul β] : CommMonoidWithZero C(α, β) :=
  coe_injective.CommMonoidWithZero _ coe_zero coe_one coe_mul coe_pow

@[to_additive]
instance [LocallyCompactSpace α] [Mul β] [ContinuousMul β] : ContinuousMul C(α, β) :=
  ⟨by
    refine' continuous_of_continuous_uncurry _ _
    have h1 : Continuous fun x : (C(α, β) × C(α, β)) × α => x.fst.fst x.snd :=
      continuous_eval'.comp (continuous_fst.prod_map continuous_id)
    have h2 : Continuous fun x : (C(α, β) × C(α, β)) × α => x.fst.snd x.snd :=
      continuous_eval'.comp (continuous_snd.prod_map continuous_id)
    exact h1.mul h2⟩

/-- Coercion to a function as an `monoid_hom`. Similar to `monoid_hom.coe_fn`. -/
@[to_additive "Coercion to a function as an `add_monoid_hom`. Similar to `add_monoid_hom.coe_fn`.",
  simps]
def coeFnMonoidHom [Monoid β] [ContinuousMul β] : C(α, β) →* α → β
    where
  toFun := coeFn
  map_one' := coe_one
  map_mul' := coe_mul
#align continuous_map.coe_fn_monoid_hom ContinuousMap.coeFnMonoidHom
#align continuous_map.coe_fn_add_monoid_hom ContinuousMap.coeFnAddMonoidHom

variable (α)

/-- Composition on the left by a (continuous) homomorphism of topological monoids, as a
`monoid_hom`. Similar to `monoid_hom.comp_left`. -/
@[to_additive
      "Composition on the left by a (continuous) homomorphism of topological `add_monoid`s,\nas an `add_monoid_hom`. Similar to `add_monoid_hom.comp_left`.",
  simps]
protected def MonoidHom.compLeftContinuous {γ : Type _} [Monoid β] [ContinuousMul β]
    [TopologicalSpace γ] [Monoid γ] [ContinuousMul γ] (g : β →* γ) (hg : Continuous g) :
    C(α, β) →* C(α, γ) where
  toFun f := (⟨g, hg⟩ : C(β, γ)).comp f
  map_one' := ext fun x => g.map_one
  map_mul' f₁ f₂ := ext fun x => g.map_mul _ _
#align monoid_hom.comp_left_continuous MonoidHom.compLeftContinuous
#align add_monoid_hom.comp_left_continuous AddMonoidHom.compLeftContinuous

variable {α}

/-- Composition on the right as a `monoid_hom`. Similar to `monoid_hom.comp_hom'`. -/
@[to_additive
      "Composition on the right as an `add_monoid_hom`. Similar to\n`add_monoid_hom.comp_hom'`.",
  simps]
def compMonoidHom' {γ : Type _} [TopologicalSpace γ] [MulOneClass γ] [ContinuousMul γ]
    (g : C(α, β)) : C(β, γ) →* C(α, γ)
    where
  toFun f := f.comp g
  map_one' := one_comp g
  map_mul' f₁ f₂ := mul_comp f₁ f₂ g
#align continuous_map.comp_monoid_hom' ContinuousMap.compMonoidHom'
#align continuous_map.comp_add_monoid_hom' ContinuousMap.compAddMonoidHom'

open BigOperators

@[simp, to_additive]
theorem coe_prod [CommMonoid β] [ContinuousMul β] {ι : Type _} (s : Finset ι) (f : ι → C(α, β)) :
    ⇑(∏ i in s, f i) = ∏ i in s, (f i : α → β) :=
  (coeFnMonoidHom : C(α, β) →* _).map_prod f s
#align continuous_map.coe_prod ContinuousMap.coe_prod
#align continuous_map.coe_sum ContinuousMap.coe_sum

@[to_additive]
theorem prod_apply [CommMonoid β] [ContinuousMul β] {ι : Type _} (s : Finset ι) (f : ι → C(α, β))
    (a : α) : (∏ i in s, f i) a = ∏ i in s, f i a := by simp
#align continuous_map.prod_apply ContinuousMap.prod_apply
#align continuous_map.sum_apply ContinuousMap.sum_apply

@[to_additive]
instance [Group β] [TopologicalGroup β] : Group C(α, β) :=
  coe_injective.Group _ coe_one coe_mul coe_inv coe_div coe_pow coe_zpow

@[to_additive]
instance [CommGroup β] [TopologicalGroup β] : CommGroup C(α, β) :=
  coe_injective.CommGroup _ coe_one coe_mul coe_inv coe_div coe_pow coe_zpow

@[to_additive]
instance [CommGroup β] [TopologicalGroup β] : TopologicalGroup C(α, β)
    where
  continuous_mul := by
    letI : UniformSpace β := TopologicalGroup.toUniformSpace β
    have : UniformGroup β := comm_topologicalGroup_is_uniform
    rw [continuous_iff_continuousAt]
    rintro ⟨f, g⟩
    rw [ContinuousAt, tendsto_iff_forall_compact_tendsto_uniformly_on, nhds_prod_eq]
    exact fun K hK =>
      uniform_continuous_mul.comp_tendsto_uniformly_on
        ((tendsto_iff_forall_compact_tendsto_uniformly_on.mp Filter.tendsto_id K hK).Prod
          (tendsto_iff_forall_compact_tendsto_uniformly_on.mp Filter.tendsto_id K hK))
  continuous_inv := by
    letI : UniformSpace β := TopologicalGroup.toUniformSpace β
    have : UniformGroup β := comm_topologicalGroup_is_uniform
    rw [continuous_iff_continuousAt]
    intro f
    rw [ContinuousAt, tendsto_iff_forall_compact_tendsto_uniformly_on]
    exact fun K hK =>
      uniform_continuous_inv.comp_tendsto_uniformly_on
        (tendsto_iff_forall_compact_tendsto_uniformly_on.mp Filter.tendsto_id K hK)

-- TODO: rewrite the next three lemmas for products and deduce sum case via `to_additive`, once
-- definition of `tprod` is in place
/-- If `α` is locally compact, and an infinite sum of functions in `C(α, β)`
converges to `g` (for the compact-open topology), then the pointwise sum converges to `g x` for
all `x ∈ α`. -/
theorem hasSum_apply {γ : Type _} [LocallyCompactSpace α] [AddCommMonoid β] [ContinuousAdd β]
    {f : γ → C(α, β)} {g : C(α, β)} (hf : HasSum f g) (x : α) : HasSum (fun i : γ => f i x) (g x) :=
  by
  let evₓ : AddMonoidHom C(α, β) β := (Pi.evalAddMonoidHom _ x).comp coe_fn_add_monoid_hom
  exact hf.map evₓ (ContinuousMap.continuous_eval_const' x)
#align continuous_map.has_sum_apply ContinuousMap.hasSum_apply

theorem summable_apply [LocallyCompactSpace α] [AddCommMonoid β] [ContinuousAdd β] {γ : Type _}
    {f : γ → C(α, β)} (hf : Summable f) (x : α) : Summable fun i : γ => f i x :=
  (hasSum_apply hf.HasSum x).Summable
#align continuous_map.summable_apply ContinuousMap.summable_apply

theorem tsum_apply [LocallyCompactSpace α] [T2Space β] [AddCommMonoid β] [ContinuousAdd β]
    {γ : Type _} {f : γ → C(α, β)} (hf : Summable f) (x : α) :
    (∑' i : γ, f i x) = (∑' i : γ, f i) x :=
  (hasSum_apply hf.HasSum x).tsum_eq
#align continuous_map.tsum_apply ContinuousMap.tsum_apply

end ContinuousMap

end GroupStructure

section RingStructure

/-!
### Ring stucture

In this section we show that continuous functions valued in a topological semiring `R` inherit
the structure of a ring.
-/

section Subtype

/-- The subsemiring of continuous maps `α → β`. -/
def continuousSubsemiring (α : Type _) (R : Type _) [TopologicalSpace α] [TopologicalSpace R]
    [Semiring R] [TopologicalSemiring R] : Subsemiring (α → R) :=
  { continuousAddSubmonoid α R, continuousSubmonoid α R with }
#align continuous_subsemiring continuousSubsemiring

/-- The subring of continuous maps `α → β`. -/
def continuousSubring (α : Type _) (R : Type _) [TopologicalSpace α] [TopologicalSpace R] [Ring R]
    [TopologicalRing R] : Subring (α → R) :=
  { continuousSubsemiring α R, continuousAddSubgroup α R with }
#align continuous_subring continuousSubring

end Subtype

namespace ContinuousMap

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β]
    [NonUnitalNonAssocSemiring β] [TopologicalSemiring β] : NonUnitalNonAssocSemiring C(α, β) :=
  coe_injective.NonUnitalNonAssocSemiring _ coe_zero coe_add coe_mul coe_nsmul

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [NonUnitalSemiring β]
    [TopologicalSemiring β] : NonUnitalSemiring C(α, β) :=
  coe_injective.NonUnitalSemiring _ coe_zero coe_add coe_mul coe_nsmul

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [AddMonoidWithOne β]
    [ContinuousAdd β] : AddMonoidWithOne C(α, β) :=
  coe_injective.AddMonoidWithOne _ coe_zero coe_one coe_add coe_nsmul coe_nat_cast

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [NonAssocSemiring β]
    [TopologicalSemiring β] : NonAssocSemiring C(α, β) :=
  coe_injective.NonAssocSemiring _ coe_zero coe_one coe_add coe_mul coe_nsmul coe_nat_cast

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [Semiring β]
    [TopologicalSemiring β] : Semiring C(α, β) :=
  coe_injective.Semiring _ coe_zero coe_one coe_add coe_mul coe_nsmul coe_pow coe_nat_cast

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β]
    [NonUnitalNonAssocRing β] [TopologicalRing β] : NonUnitalNonAssocRing C(α, β) :=
  coe_injective.NonUnitalNonAssocRing _ coe_zero coe_add coe_mul coe_neg coe_sub coe_nsmul coe_zsmul

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [NonUnitalRing β]
    [TopologicalRing β] : NonUnitalRing C(α, β) :=
  coe_injective.NonUnitalRing _ coe_zero coe_add coe_mul coe_neg coe_sub coe_nsmul coe_zsmul

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [NonAssocRing β]
    [TopologicalRing β] : NonAssocRing C(α, β) :=
  coe_injective.NonAssocRing _ coe_zero coe_one coe_add coe_mul coe_neg coe_sub coe_nsmul coe_zsmul
    coe_nat_cast coe_int_cast

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [Ring β]
    [TopologicalRing β] : Ring C(α, β) :=
  coe_injective.Ring _ coe_zero coe_one coe_add coe_mul coe_neg coe_sub coe_nsmul coe_zsmul coe_pow
    coe_nat_cast coe_int_cast

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β]
    [NonUnitalCommSemiring β] [TopologicalSemiring β] : NonUnitalCommSemiring C(α, β) :=
  coe_injective.NonUnitalCommSemiring _ coe_zero coe_add coe_mul coe_nsmul

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [CommSemiring β]
    [TopologicalSemiring β] : CommSemiring C(α, β) :=
  coe_injective.CommSemiring _ coe_zero coe_one coe_add coe_mul coe_nsmul coe_pow coe_nat_cast

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [NonUnitalCommRing β]
    [TopologicalRing β] : NonUnitalCommRing C(α, β) :=
  coe_injective.NonUnitalCommRing _ coe_zero coe_add coe_mul coe_neg coe_sub coe_nsmul coe_zsmul

instance {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [CommRing β]
    [TopologicalRing β] : CommRing C(α, β) :=
  coe_injective.CommRing _ coe_zero coe_one coe_add coe_mul coe_neg coe_sub coe_nsmul coe_zsmul
    coe_pow coe_nat_cast coe_int_cast

/-- Composition on the left by a (continuous) homomorphism of topological semirings, as a
`ring_hom`.  Similar to `ring_hom.comp_left`. -/
@[simps]
protected def RingHom.compLeftContinuous (α : Type _) {β : Type _} {γ : Type _} [TopologicalSpace α]
    [TopologicalSpace β] [Semiring β] [TopologicalSemiring β] [TopologicalSpace γ] [Semiring γ]
    [TopologicalSemiring γ] (g : β →+* γ) (hg : Continuous g) : C(α, β) →+* C(α, γ) :=
  { g.toMonoidHom.compLeftContinuous α hg, g.toAddMonoidHom.compLeftContinuous α hg with }
#align ring_hom.comp_left_continuous RingHom.compLeftContinuous

/-- Coercion to a function as a `ring_hom`. -/
@[simps]
def coeFnRingHom {α : Type _} {β : Type _} [TopologicalSpace α] [TopologicalSpace β] [Semiring β]
    [TopologicalSemiring β] : C(α, β) →+* α → β :=
  { (coeFnMonoidHom : C(α, β) →* _), (coeFnAddMonoidHom : C(α, β) →+ _) with toFun := coeFn }
#align continuous_map.coe_fn_ring_hom ContinuousMap.coeFnRingHom

end ContinuousMap

end RingStructure

attribute [local ext] Subtype.eq

section ModuleStructure

/-!
### Semiodule stucture

In this section we show that continuous functions valued in a topological module `M` over a
topological semiring `R` inherit the structure of a module.
-/

section Subtype

variable (α : Type _) [TopologicalSpace α]

variable (R : Type _) [Semiring R]

variable (M : Type _) [TopologicalSpace M] [AddCommGroup M]

variable [Module R M] [ContinuousConstSMul R M] [TopologicalAddGroup M]

/-- The `R`-submodule of continuous maps `α → M`. -/
def continuousSubmodule : Submodule R (α → M) :=
  {
    continuousAddSubgroup α
      M with
    carrier := { f : α → M | Continuous f }
    smul_mem' := fun c f hf => hf.const_smul c }
#align continuous_submodule continuousSubmodule

end Subtype

namespace ContinuousMap

variable {α β : Type _} [TopologicalSpace α] [TopologicalSpace β] {R R₁ : Type _} {M : Type _}
  [TopologicalSpace M] {M₂ : Type _} [TopologicalSpace M₂]

@[to_additive ContinuousMap.hasVadd]
instance [SMul R M] [ContinuousConstSMul R M] : SMul R C(α, M) :=
  ⟨fun r f => ⟨r • f, f.Continuous.const_smul r⟩⟩

@[to_additive]
instance [LocallyCompactSpace α] [SMul R M] [ContinuousConstSMul R M] :
    ContinuousConstSMul R C(α, M) :=
  ⟨fun γ => continuous_of_continuous_uncurry _ (continuous_eval'.const_smul γ)⟩

@[to_additive]
instance [LocallyCompactSpace α] [TopologicalSpace R] [SMul R M] [ContinuousSMul R M] :
    ContinuousSMul R C(α, M) :=
  ⟨by
    refine' continuous_of_continuous_uncurry _ _
    have h : Continuous fun x : (R × C(α, M)) × α => x.fst.snd x.snd :=
      continuous_eval'.comp (continuous_snd.prod_map continuous_id)
    exact (continuous_fst.comp continuous_fst).smul h⟩

@[simp, norm_cast, to_additive]
theorem coe_smul [SMul R M] [ContinuousConstSMul R M] (c : R) (f : C(α, M)) : ⇑(c • f) = c • f :=
  rfl
#align continuous_map.coe_smul ContinuousMap.coe_smul
#align continuous_map.coe_vadd ContinuousMap.coe_vadd

@[to_additive]
theorem smul_apply [SMul R M] [ContinuousConstSMul R M] (c : R) (f : C(α, M)) (a : α) :
    (c • f) a = c • f a :=
  rfl
#align continuous_map.smul_apply ContinuousMap.smul_apply
#align continuous_map.vadd_apply ContinuousMap.vadd_apply

@[simp, to_additive]
theorem smul_comp [SMul R M] [ContinuousConstSMul R M] (r : R) (f : C(β, M)) (g : C(α, β)) :
    (r • f).comp g = r • f.comp g :=
  rfl
#align continuous_map.smul_comp ContinuousMap.smul_comp
#align continuous_map.vadd_comp ContinuousMap.vadd_comp

@[to_additive]
instance [SMul R M] [ContinuousConstSMul R M] [SMul R₁ M] [ContinuousConstSMul R₁ M]
    [SMulCommClass R R₁ M] : SMulCommClass R R₁ C(α, M)
    where smul_comm _ _ _ := ext fun _ => smul_comm _ _ _

instance [SMul R M] [ContinuousConstSMul R M] [SMul R₁ M] [ContinuousConstSMul R₁ M] [SMul R R₁]
    [IsScalarTower R R₁ M] : IsScalarTower R R₁ C(α, M)
    where smul_assoc _ _ _ := ext fun _ => smul_assoc _ _ _

instance [SMul R M] [SMul Rᵐᵒᵖ M] [ContinuousConstSMul R M] [IsCentralScalar R M] :
    IsCentralScalar R C(α, M) where op_smul_eq_smul _ _ := ext fun _ => op_smul_eq_smul _ _

instance [Monoid R] [MulAction R M] [ContinuousConstSMul R M] : MulAction R C(α, M) :=
  Function.Injective.mulAction _ coe_injective coe_smul

instance [Monoid R] [AddMonoid M] [DistribMulAction R M] [ContinuousAdd M]
    [ContinuousConstSMul R M] : DistribMulAction R C(α, M) :=
  Function.Injective.distribMulAction coeFnAddMonoidHom coe_injective coe_smul

variable [Semiring R] [AddCommMonoid M] [AddCommMonoid M₂]

variable [ContinuousAdd M] [Module R M] [ContinuousConstSMul R M]

variable [ContinuousAdd M₂] [Module R M₂] [ContinuousConstSMul R M₂]

instance module : Module R C(α, M) :=
  Function.Injective.module R coeFnAddMonoidHom coe_injective coe_smul
#align continuous_map.module ContinuousMap.module

variable (R)

/-- Composition on the left by a continuous linear map, as a `linear_map`.
Similar to `linear_map.comp_left`. -/
@[simps]
protected def ContinuousLinearMap.compLeftContinuous (α : Type _) [TopologicalSpace α]
    (g : M →L[R] M₂) : C(α, M) →ₗ[R] C(α, M₂) :=
  { g.toLinearMap.toAddMonoidHom.compLeftContinuous α g.Continuous with
    map_smul' := fun c f => ext fun x => g.map_smul' c _ }
#align continuous_linear_map.comp_left_continuous ContinuousLinearMap.compLeftContinuous

/-- Coercion to a function as a `linear_map`. -/
@[simps]
def coeFnLinearMap : C(α, M) →ₗ[R] α → M :=
  { (coeFnAddMonoidHom : C(α, M) →+ _) with
    toFun := coeFn
    map_smul' := coe_smul }
#align continuous_map.coe_fn_linear_map ContinuousMap.coeFnLinearMap

end ContinuousMap

end ModuleStructure

section AlgebraStructure

/-!
### Algebra structure

In this section we show that continuous functions valued in a topological algebra `A` over a ring
`R` inherit the structure of an algebra. Note that the hypothesis that `A` is a topological algebra
is obtained by requiring that `A` be both a `has_continuous_smul` and a `topological_semiring`.-/

section Subtype

variable {α : Type _} [TopologicalSpace α] {R : Type _} [CommSemiring R] {A : Type _}
  [TopologicalSpace A] [Semiring A] [Algebra R A] [TopologicalSemiring A]

/-- The `R`-subalgebra of continuous maps `α → A`. -/
def continuousSubalgebra : Subalgebra R (α → A) :=
  {
    continuousSubsemiring α
      A with
    carrier := { f : α → A | Continuous f }
    algebraMap_mem' := fun r => (continuous_const : Continuous fun x : α => algebraMap R A r) }
#align continuous_subalgebra continuousSubalgebra

end Subtype

section ContinuousMap

variable {α : Type _} [TopologicalSpace α] {R : Type _} [CommSemiring R] {A : Type _}
  [TopologicalSpace A] [Semiring A] [Algebra R A] [TopologicalSemiring A] {A₂ : Type _}
  [TopologicalSpace A₂] [Semiring A₂] [Algebra R A₂] [TopologicalSemiring A₂]

/-- Continuous constant functions as a `ring_hom`. -/
def ContinuousMap.c : R →+* C(α, A)
    where
  toFun := fun c : R => ⟨fun x : α => (algebraMap R A) c, continuous_const⟩
  map_one' := by ext x <;> exact (algebraMap R A).map_one
  map_mul' c₁ c₂ := by ext x <;> exact (algebraMap R A).map_mul _ _
  map_zero' := by ext x <;> exact (algebraMap R A).map_zero
  map_add' c₁ c₂ := by ext x <;> exact (algebraMap R A).map_add _ _
#align continuous_map.C ContinuousMap.c

@[simp]
theorem ContinuousMap.c_apply (r : R) (a : α) : ContinuousMap.c r a = algebraMap R A r :=
  rfl
#align continuous_map.C_apply ContinuousMap.c_apply

instance ContinuousMap.algebra : Algebra R C(α, A)
    where
  toRingHom := ContinuousMap.c
  commutes' c f := by ext x <;> exact Algebra.commutes' _ _
  smul_def' c f := by ext x <;> exact Algebra.smul_def' _ _
#align continuous_map.algebra ContinuousMap.algebra

variable (R)

/-- Composition on the left by a (continuous) homomorphism of topological `R`-algebras, as an
`alg_hom`. Similar to `alg_hom.comp_left`. -/
@[simps]
protected def AlgHom.compLeftContinuous {α : Type _} [TopologicalSpace α] (g : A →ₐ[R] A₂)
    (hg : Continuous g) : C(α, A) →ₐ[R] C(α, A₂) :=
  { g.toRingHom.compLeftContinuous α hg with
    commutes' := fun c => ContinuousMap.ext fun _ => g.commutes' _ }
#align alg_hom.comp_left_continuous AlgHom.compLeftContinuous

variable (A)

/-- Precomposition of functions into a normed ring by a continuous map is an algebra homomorphism.
-/
@[simps]
def ContinuousMap.compRightAlgHom {α β : Type _} [TopologicalSpace α] [TopologicalSpace β]
    (f : C(α, β)) : C(β, A) →ₐ[R] C(α, A)
    where
  toFun g := g.comp f
  map_zero' := by
    ext
    rfl
  map_add' g₁ g₂ := by
    ext
    rfl
  map_one' := by
    ext
    rfl
  map_mul' g₁ g₂ := by
    ext
    rfl
  commutes' r := by
    ext
    rfl
#align continuous_map.comp_right_alg_hom ContinuousMap.compRightAlgHom

variable {A}

/-- Coercion to a function as an `alg_hom`. -/
@[simps]
def ContinuousMap.coeFnAlgHom : C(α, A) →ₐ[R] α → A :=
  {
    (ContinuousMap.coeFnRingHom :
      C(α, A) →+* _) with
    toFun := coeFn
    commutes' := fun r => rfl }
#align continuous_map.coe_fn_alg_hom ContinuousMap.coeFnAlgHom

variable {R}

/-- A version of `separates_points` for subalgebras of the continuous functions,
used for stating the Stone-Weierstrass theorem.
-/
abbrev Subalgebra.SeparatesPoints (s : Subalgebra R C(α, A)) : Prop :=
  Set.SeparatesPoints ((fun f : C(α, A) => (f : α → A)) '' (s : Set C(α, A)))
#align subalgebra.separates_points Subalgebra.SeparatesPoints

theorem Subalgebra.separatesPoints_monotone :
    Monotone fun s : Subalgebra R C(α, A) => s.SeparatesPoints := fun s s' r h x y n =>
  by
  obtain ⟨f, m, w⟩ := h n
  rcases m with ⟨f, ⟨m, rfl⟩⟩
  exact ⟨_, ⟨f, ⟨r m, rfl⟩⟩, w⟩
#align subalgebra.separates_points_monotone Subalgebra.separatesPoints_monotone

@[simp]
theorem algebraMap_apply (k : R) (a : α) : algebraMap R C(α, A) k a = k • 1 :=
  by
  rw [Algebra.algebraMap_eq_smul_one]
  rfl
#align algebra_map_apply algebraMap_apply

variable {𝕜 : Type _} [TopologicalSpace 𝕜]

/-- A set of continuous maps "separates points strongly"
if for each pair of distinct points there is a function with specified values on them.

We give a slightly unusual formulation, where the specified values are given by some
function `v`, and we ask `f x = v x ∧ f y = v y`. This avoids needing a hypothesis `x ≠ y`.

In fact, this definition would work perfectly well for a set of non-continuous functions,
but as the only current use case is in the Stone-Weierstrass theorem,
writing it this way avoids having to deal with casts inside the set.
(This may need to change if we do Stone-Weierstrass on non-compact spaces,
where the functions would be continuous functions vanishing at infinity.)
-/
def Set.SeparatesPointsStrongly (s : Set C(α, 𝕜)) : Prop :=
  ∀ (v : α → 𝕜) (x y : α), ∃ f : s, (f x : 𝕜) = v x ∧ f y = v y
#align set.separates_points_strongly Set.SeparatesPointsStrongly

variable [Field 𝕜] [TopologicalRing 𝕜]

/-- Working in continuous functions into a topological field,
a subalgebra of functions that separates points also separates points strongly.

By the hypothesis, we can find a function `f` so `f x ≠ f y`.
By an affine transformation in the field we can arrange so that `f x = a` and `f x = b`.
-/
theorem Subalgebra.SeparatesPoints.strongly {s : Subalgebra 𝕜 C(α, 𝕜)} (h : s.SeparatesPoints) :
    (s : Set C(α, 𝕜)).SeparatesPointsStrongly := fun v x y =>
  by
  by_cases n : x = y
  · subst n
    use (v x • 1 : C(α, 𝕜))
    · apply s.smul_mem
      apply s.one_mem
    · simp [coeFn_coe_base']
  obtain ⟨f, ⟨f, ⟨m, rfl⟩⟩, w⟩ := h n
  replace w : f x - f y ≠ 0 := sub_ne_zero_of_ne w
  let a := v x
  let b := v y
  let f' := ((b - a) * (f x - f y)⁻¹) • (ContinuousMap.c (f x) - f) + ContinuousMap.c a
  refine' ⟨⟨f', _⟩, _, _⟩
  · simp only [f', SetLike.mem_coe, Subalgebra.mem_toSubmodule]
    -- TODO should there be a tactic for this?
    -- We could add an attribute `@[subobject_mem]`, and a tactic
    -- ``def subobject_mem := `[solve_by_elim with subobject_mem { max_depth := 10 }]``
    solve_by_elim (config := { max_depth := 6 }) [Subalgebra.add_mem, Subalgebra.smul_mem,
      Subalgebra.sub_mem, Subalgebra.algebraMap_mem]
  · simp [f', coeFn_coe_base']
  · simp [f', coeFn_coe_base', inv_mul_cancel_right₀ w]
#align subalgebra.separates_points.strongly Subalgebra.SeparatesPoints.strongly

end ContinuousMap

instance ContinuousMap.subsingleton_subalgebra (α : Type _) [TopologicalSpace α] (R : Type _)
    [CommSemiring R] [TopologicalSpace R] [TopologicalSemiring R] [Subsingleton α] :
    Subsingleton (Subalgebra R C(α, R)) := by
  fconstructor
  intro s₁ s₂
  by_cases n : Nonempty α
  · obtain ⟨x⟩ := n
    ext f
    have h : f = algebraMap R C(α, R) (f x) := by
      ext x'
      simp only [mul_one, Algebra.id.smul_eq_mul, algebraMap_apply]
      congr
    rw [h]
    simp only [Subalgebra.algebraMap_mem]
  · ext f
    have h : f = 0 := by
      ext x'
      exact False.elim (n ⟨x'⟩)
    subst h
    simp only [Subalgebra.zero_mem]
#align continuous_map.subsingleton_subalgebra ContinuousMap.subsingleton_subalgebra

end AlgebraStructure

section ModuleOverContinuousFunctions

/-!
### Structure as module over scalar functions

If `M` is a module over `R`, then we show that the space of continuous functions from `α` to `M`
is naturally a module over the ring of continuous functions from `α` to `R`. -/

namespace ContinuousMap

instance hasSmul' {α : Type _} [TopologicalSpace α] {R : Type _} [Semiring R] [TopologicalSpace R]
    {M : Type _} [TopologicalSpace M] [AddCommMonoid M] [Module R M] [ContinuousSMul R M] :
    SMul C(α, R) C(α, M) :=
  ⟨fun f g => ⟨fun x => f x • g x, Continuous.smul f.2 g.2⟩⟩
#align continuous_map.has_smul' ContinuousMap.hasSmul'

instance module' {α : Type _} [TopologicalSpace α] (R : Type _) [Ring R] [TopologicalSpace R]
    [TopologicalRing R] (M : Type _) [TopologicalSpace M] [AddCommMonoid M] [ContinuousAdd M]
    [Module R M] [ContinuousSMul R M] : Module C(α, R) C(α, M)
    where
  smul := (· • ·)
  smul_add c f g := by ext x <;> exact smul_add (c x) (f x) (g x)
  add_smul c₁ c₂ f := by ext x <;> exact add_smul (c₁ x) (c₂ x) (f x)
  mul_smul c₁ c₂ f := by ext x <;> exact mul_smul (c₁ x) (c₂ x) (f x)
  one_smul f := by ext x <;> exact one_smul R (f x)
  zero_smul f := by ext x <;> exact zero_smul _ _
  smul_zero r := by ext x <;> exact smul_zero _
#align continuous_map.module' ContinuousMap.module'

end ContinuousMap

end ModuleOverContinuousFunctions

/-!
We now provide formulas for `f ⊓ g` and `f ⊔ g`, where `f g : C(α, β)`,
in terms of `continuous_map.abs`.
-/

section

variable {R : Type _} [LinearOrderedField R]

-- TODO:
-- This lemma (and the next) could go all the way back in `algebra.order.field`,
-- except that it is tedious to prove without tactics.
-- Rather than stranding it at some intermediate location,
-- it's here, immediately prior to the point of use.
theorem min_eq_half_add_sub_abs_sub {x y : R} : min x y = 2⁻¹ * (x + y - |x - y|) := by
  cases' le_total x y with h h <;> field_simp [h, abs_of_nonneg, abs_of_nonpos, mul_two] <;> abel
#align min_eq_half_add_sub_abs_sub min_eq_half_add_sub_abs_sub

theorem max_eq_half_add_add_abs_sub {x y : R} : max x y = 2⁻¹ * (x + y + |x - y|) := by
  cases' le_total x y with h h <;> field_simp [h, abs_of_nonneg, abs_of_nonpos, mul_two] <;> abel
#align max_eq_half_add_add_abs_sub max_eq_half_add_add_abs_sub

end

namespace ContinuousMap

section Lattice

variable {α : Type _} [TopologicalSpace α]

variable {β : Type _} [LinearOrderedField β] [TopologicalSpace β] [OrderTopology β]
  [TopologicalRing β]

theorem inf_eq (f g : C(α, β)) : f ⊓ g = (2⁻¹ : β) • (f + g - |f - g|) :=
  ext fun x => by simpa using min_eq_half_add_sub_abs_sub
#align continuous_map.inf_eq ContinuousMap.inf_eq

-- Not sure why this is grosser than `inf_eq`:
theorem sup_eq (f g : C(α, β)) : f ⊔ g = (2⁻¹ : β) • (f + g + |f - g|) :=
  ext fun x => by simpa [mul_add] using @max_eq_half_add_add_abs_sub _ _ (f x) (g x)
#align continuous_map.sup_eq ContinuousMap.sup_eq

end Lattice

/-!
### Star structure

If `β` has a continuous star operation, we put a star structure on `C(α, β)` by using the
star operation pointwise.

If `β` is a ⋆-ring, then `C(α, β)` inherits a ⋆-ring structure.

If `β` is a ⋆-ring and a ⋆-module over `R`, then the space of continuous functions from `α` to `β`
is a ⋆-module over `R`.

-/

section StarStructure

variable {R α β : Type _}

variable [TopologicalSpace α] [TopologicalSpace β]

section Star

variable [Star β] [ContinuousStar β]

instance : Star C(α, β) where unit f := starContinuousMap.comp f

@[simp]
theorem coe_star (f : C(α, β)) : ⇑(star f) = star f :=
  rfl
#align continuous_map.coe_star ContinuousMap.coe_star

@[simp]
theorem star_apply (f : C(α, β)) (x : α) : star f x = star (f x) :=
  rfl
#align continuous_map.star_apply ContinuousMap.star_apply

end Star

instance [InvolutiveStar β] [ContinuousStar β] : InvolutiveStar C(α, β)
    where star_involutive f := ext fun x => star_star _

instance [AddMonoid β] [ContinuousAdd β] [StarAddMonoid β] [ContinuousStar β] :
    StarAddMonoid C(α, β) where star_add f g := ext fun x => star_add _ _

instance [Semigroup β] [ContinuousMul β] [StarSemigroup β] [ContinuousStar β] :
    StarSemigroup C(α, β) where star_mul f g := ext fun x => star_mul _ _

instance [NonUnitalSemiring β] [TopologicalSemiring β] [StarRing β] [ContinuousStar β] :
    StarRing C(α, β) :=
  { ContinuousMap.starAddMonoid with }

instance [Star R] [Star β] [SMul R β] [StarModule R β] [ContinuousStar β]
    [ContinuousConstSMul R β] : StarModule R C(α, β)
    where star_smul k f := ext fun x => star_smul _ _

end StarStructure

variable {X Y Z : Type _} [TopologicalSpace X] [TopologicalSpace Y] [TopologicalSpace Z]

variable (𝕜 : Type _) [CommSemiring 𝕜]

variable (A : Type _) [TopologicalSpace A] [Semiring A] [TopologicalSemiring A] [StarRing A]

variable [ContinuousStar A] [Algebra 𝕜 A]

/-- The functorial map taking `f : C(X, Y)` to `C(Y, A) →⋆ₐ[𝕜] C(X, A)` given by pre-composition
with the continuous function `f`. See `continuous_map.comp_monoid_hom'` and
`continuous_map.comp_add_monoid_hom'`, `continuous_map.comp_right_alg_hom` for bundlings of
pre-composition into a `monoid_hom`, an `add_monoid_hom` and an `alg_hom`, respectively, under
suitable assumptions on `A`. -/
@[simps]
def compStarAlgHom' (f : C(X, Y)) : C(Y, A) →⋆ₐ[𝕜] C(X, A)
    where
  toFun g := g.comp f
  map_one' := one_comp _
  map_mul' _ _ := rfl
  map_zero' := zero_comp _
  map_add' _ _ := rfl
  commutes' _ := rfl
  map_star' _ := rfl
#align continuous_map.comp_star_alg_hom' ContinuousMap.compStarAlgHom'

/-- `continuous_map.comp_star_alg_hom'` sends the identity continuous map to the identity
`star_alg_hom` -/
theorem compStarAlgHom'_id : compStarAlgHom' 𝕜 A (ContinuousMap.id X) = StarAlgHom.id 𝕜 C(X, A) :=
  StarAlgHom.ext fun _ => ContinuousMap.ext fun _ => rfl
#align continuous_map.comp_star_alg_hom'_id ContinuousMap.compStarAlgHom'_id

/-- `continuous_map.comp_star_alg_hom` is functorial. -/
theorem compStarAlgHom'_comp (g : C(Y, Z)) (f : C(X, Y)) :
    compStarAlgHom' 𝕜 A (g.comp f) = (compStarAlgHom' 𝕜 A f).comp (compStarAlgHom' 𝕜 A g) :=
  StarAlgHom.ext fun _ => ContinuousMap.ext fun _ => rfl
#align continuous_map.comp_star_alg_hom'_comp ContinuousMap.compStarAlgHom'_comp

section Periodicity

/-! ### Summing translates of a function -/

/-- Summing the translates of `f` by `ℤ • p` gives a map which is periodic with period `p`.
(This is true without any convergence conditions, since if the sum doesn't converge it is taken to
be the zero map, which is periodic.) -/
theorem periodic_tsum_comp_add_zsmul [LocallyCompactSpace X] [AddCommGroup X]
    [TopologicalAddGroup X] [AddCommMonoid Y] [ContinuousAdd Y] [T2Space Y] (f : C(X, Y)) (p : X) :
    Function.Periodic (⇑(∑' n : ℤ, f.comp (ContinuousMap.addRight (n • p)))) p :=
  by
  intro x
  by_cases h : Summable fun n : ℤ => f.comp (ContinuousMap.addRight (n • p))
  · convert congr_arg (fun f : C(X, Y) => f x) ((Equiv.addRight (1 : ℤ)).tsum_eq _) using 1
    simp_rw [← tsum_apply h, ← tsum_apply ((Equiv.addRight (1 : ℤ)).summable_iff.mpr h),
      Equiv.coe_addRight, comp_apply, coe_add_right, add_one_zsmul, add_comm (_ • p) p, ← add_assoc]
  · rw [tsum_eq_zero_of_not_summable h]
    simp only [coe_zero, Pi.zero_apply]
#align continuous_map.periodic_tsum_comp_add_zsmul ContinuousMap.periodic_tsum_comp_add_zsmul

end Periodicity

end ContinuousMap

namespace Homeomorph

variable {X Y : Type _} [TopologicalSpace X] [TopologicalSpace Y]

variable (𝕜 : Type _) [CommSemiring 𝕜]

variable (A : Type _) [TopologicalSpace A] [Semiring A] [TopologicalSemiring A] [StarRing A]

variable [ContinuousStar A] [Algebra 𝕜 A]

/-- `continuous_map.comp_star_alg_hom'` as a `star_alg_equiv` when the continuous map `f` is
actually a homeomorphism. -/
@[simps]
def compStarAlgEquiv' (f : X ≃ₜ Y) : C(Y, A) ≃⋆ₐ[𝕜] C(X, A) :=
  {
    f.toContinuousMap.compStarAlgHom' 𝕜
      A with
    toFun := (f : C(X, Y)).compStarAlgHom' 𝕜 A
    invFun := (f.symm : C(Y, X)).compStarAlgHom' 𝕜 A
    left_inv := fun g => by
      simp only [ContinuousMap.compStarAlgHom'_apply, ContinuousMap.comp_assoc,
        to_continuous_map_comp_symm, ContinuousMap.comp_id]
    right_inv := fun g => by
      simp only [ContinuousMap.compStarAlgHom'_apply, ContinuousMap.comp_assoc,
        symm_comp_to_continuous_map, ContinuousMap.comp_id]
    map_smul' := fun k a => map_smul (f.toContinuousMap.compStarAlgHom' 𝕜 A) k a }
#align homeomorph.comp_star_alg_equiv' Homeomorph.compStarAlgEquiv'

end Homeomorph


/-
Copyright (c) 2022 Alexander Bentkamp. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alexander Bentkamp, Jeremy Avigad, Johan Commelin
-/
import linear_algebra.matrix.nonsingular_inverse
import linear_algebra.matrix.pos_def

/-! # Schur complement

This file proves properties of the Schur complement `D - C A⁻¹ B` of a block matrix `[A B; C D]`.

The determinant of a block matrix in terms of the Schur complement is expressed in the lemmas
`matrix.det_from_blocks₁₁` and `matrix.det_from_blocks₂₂` in the file
`linear_algebra.matrix.nonsingular_inverse`.

## Main result

 * `matrix.schur_complement_pos_semidef_iff` : If a matrix `A` is positive definite, then `[A B; Bᴴ
  D]` is postive semidefinite if and only if `D - Bᴴ A⁻¹ B` is postive semidefinite.

-/

namespace matrix

open_locale matrix
variables {n : Type*} {m : Type*} {𝕜 : Type*} [is_R_or_C 𝕜]

localized "infix ` ⊕ᵥ `:65 := sum.elim" in matrix

lemma schur_complement_eq₁₁ [fintype m] [decidable_eq m] [fintype n]
  {A : matrix m m 𝕜} (B : matrix m n 𝕜) (D : matrix n n 𝕜) (x : m → 𝕜) (y : n → 𝕜)
  [invertible A] (hA : A.is_hermitian) :
vec_mul (star (x ⊕ᵥ y)) (from_blocks A B Bᴴ D) ⬝ᵥ (x ⊕ᵥ y) =
  vec_mul (star (x + (A⁻¹ ⬝ B).mul_vec y)) A ⬝ᵥ (x + (A⁻¹ ⬝ B).mul_vec y) +
    vec_mul (star y) (D - Bᴴ ⬝ A⁻¹ ⬝ B) ⬝ᵥ y :=
begin
  simp [function.star_sum_elim, from_blocks_mul_vec, vec_mul_from_blocks, add_vec_mul,
    dot_product_mul_vec, vec_mul_sub, matrix.mul_assoc, vec_mul_mul_vec, hA.eq,
    conj_transpose_nonsing_inv, star_mul_vec],
  abel
end

end matrix

namespace matrix

open_locale matrix
variables {n : Type*} {m : Type*}
  {𝕜 : Type*} [is_R_or_C 𝕜]

lemma is_hermitian.from_blocks₁₁ [fintype m] [decidable_eq m]
  {A : matrix m m 𝕜} (B : matrix m n 𝕜) (D : matrix n n 𝕜)
  (hA : A.is_hermitian) :
  (from_blocks A B Bᴴ D).is_hermitian ↔ (D - Bᴴ ⬝ A⁻¹ ⬝ B).is_hermitian :=
begin
  have hBAB : (Bᴴ ⬝ A⁻¹ ⬝ B).is_hermitian,
  { apply is_hermitian_conj_transpose_mul_mul,
    apply hA.inv },
  rw [is_hermitian_from_blocks_iff],
  split,
  { intro h,
    apply is_hermitian.sub h.2.2.2 hBAB },
  { intro h,
    refine ⟨hA, rfl, conj_transpose_conj_transpose B, _⟩,
    rw ← sub_add_cancel D,
    apply is_hermitian.add h hBAB }
end

lemma is_hermitian.from_blocks₂₂ [fintype n] [decidable_eq n]
  (A : matrix m m 𝕜) (B : matrix m n 𝕜) {D : matrix n n 𝕜}
  (hD : D.is_hermitian) :
  (from_blocks A B Bᴴ D).is_hermitian ↔ (A - B ⬝ D⁻¹ ⬝ Bᴴ).is_hermitian :=
begin
  rw [←is_hermitian_submatrix_equiv (equiv.sum_comm n m), equiv.sum_comm_apply,
    from_blocks_submatrix_sum_swap_sum_swap],
  convert is_hermitian.from_blocks₁₁ _ _ hD; simp
end

lemma pos_semidef.from_blocks₁₁ [fintype m] [decidable_eq m] [fintype n]
  {A : matrix m m 𝕜} (B : matrix m n 𝕜) (D : matrix n n 𝕜)
  (hA : A.pos_def) [invertible A] :
  (from_blocks A B Bᴴ D).pos_semidef ↔ (D - Bᴴ ⬝ A⁻¹ ⬝ B).pos_semidef :=
begin
  rw [pos_semidef, is_hermitian.from_blocks₁₁ _ _ hA.1],
  split,
  { refine λ h, ⟨h.1, λ x, _⟩,
    have := h.2 (- ((A⁻¹ ⬝ B).mul_vec x) ⊕ᵥ x),
    rw [dot_product_mul_vec, schur_complement_eq₁₁ B D _ _ hA.1, neg_add_self,
      dot_product_zero, zero_add] at this,
    rw [dot_product_mul_vec], exact this },
  { refine λ h, ⟨h.1, λ x, _⟩,
    rw [dot_product_mul_vec, ← sum.elim_comp_inl_inr x, schur_complement_eq₁₁ B D _ _ hA.1,
      map_add],
    apply le_add_of_nonneg_of_le,
    { rw ← dot_product_mul_vec,
      apply hA.pos_semidef.2, },
    { rw ← dot_product_mul_vec, apply h.2 } }
end

lemma pos_semidef.from_blocks₂₂ [fintype m] [fintype n] [decidable_eq n]
  (A : matrix m m 𝕜) (B : matrix m n 𝕜) {D : matrix n n 𝕜}
  (hD : D.pos_def) [invertible D] :
  (from_blocks A B Bᴴ D).pos_semidef ↔ (A - B ⬝ D⁻¹ ⬝ Bᴴ).pos_semidef :=
begin
  rw [←pos_semidef_submatrix_equiv (equiv.sum_comm n m), equiv.sum_comm_apply,
    from_blocks_submatrix_sum_swap_sum_swap],
  convert pos_semidef.from_blocks₁₁ _ _ hD; apply_instance <|> simp
end

end matrix


/-
Copyright (c) 2021 Ashvni Narayanan. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Ashvni Narayanan
-/
import padic_int.properties
import topology.locally_constant.algebra
import nat_properties

/-!
# Induced functions from units
This file defines the function on `zmod d × ℤ_[p]` induced by a function on `(zmod d)ˣ × ℤ_[p]ˣ`,
and a locally constant version of it.

## Main definitions
 * `ind_fn`
 * `loc_const_ind_fn`

## Implementation notes
 * Changed `s` to `char_fn_set`
 * changed def of `ind_fn` from `dite` to `function.extend`
 * `coe_padic_to_zmod` replaced with `prod_padic_to_zmod`
 * `coprime_mul_pow` replaced with `coprime.mul_pow`
 * replace `ne_zero_of_lt` with `ne_zero_of_lt'` where needed
 * `one_lt_mul_of_ne_one` replaced with `one_lt_mul_pow_of_ne_one`
 * `exists_V_h1_1` replaced with `exists_mul_inv_val_eq`
 * `meas_E_c` removed
 * `s_nonempty` removed

## Tags
p-adic, L-function, Bernoulli measure
-/

local attribute [instance] zmod.topological_space

variables {A : Type*} [has_zero A]
variables {p : ℕ} [fact p.prime] {d : ℕ} (R : Type*) [normed_comm_ring R] (m : ℕ) {c : ℕ}
open_locale big_operators
open padic_int zmod nat locally_constant

/-- Given a function from `(zmod d)ˣ × ℤ_[p]ˣ)` to `A`, this gives the induced
  function on `(zmod d) × ℤ_[p]`, which is 0 everywhere else. -/
noncomputable abbreviation ind_fn (f : (zmod d)ˣ × ℤ_[p]ˣ → A) : zmod d × ℤ_[p] → A :=
function.extend (prod.map coe coe) f 0
--set.indicator {z : zmod d × ℤ_[p] | is_unit z.1 ∧ is_unit z.2} f
-- λ x : (zmod d × ℤ_[p]), @dite _ (is_unit x.1 ∧ is_unit x.2) (classical.dec
--   (is_unit x.fst ∧ is_unit x.snd)) (λ h, f (is_unit.unit h.1, is_unit.unit h.2)) (λ h, 0)

open function
namespace ind_fn

lemma ind_fn_def (f : (zmod d)ˣ × ℤ_[p]ˣ → A) :
  ind_fn f = function.extend (prod.map coe coe) f 0 := rfl

lemma ind_fn_eq_fun (f : (zmod d)ˣ × ℤ_[p]ˣ → A) :
  f = (ind_fn f) ∘ (prod.map (coe : units (zmod d) → zmod d) (coe : units ℤ_[p] → ℤ_[p])) :=
by { ext x, rw [ind_fn_def, comp_apply, extend_apply (injective.prod_map units.ext units.ext)], }

lemma map_ind_fn_eq_fn (f : (zmod d)ˣ × ℤ_[p]ˣ → A) {z : zmod d × ℤ_[p]}
  (h' : (is_unit z.fst ∧ is_unit z.snd)) : ind_fn f z = f (is_unit.unit h'.1, is_unit.unit h'.2) :=
begin
  conv_rhs { rw ind_fn_eq_fun f },
  congr,
  simp [prod.ext_iff, is_unit.unit_spec],
end

lemma map_ind_fn_eq_fn' (f : (zmod d)ˣ × ℤ_[p]ˣ → A) {z : (zmod d)ˣ × ℤ_[p]ˣ} :
  ind_fn f (prod.map coe coe z) = f z := by { conv_rhs { rw ind_fn_eq_fun f } }

lemma map_ind_fn_eq_zero (f : (zmod d)ˣ × ℤ_[p]ˣ → A) {z : zmod d × ℤ_[p]}
  (h' : ¬(is_unit z.fst ∧ is_unit z.snd)) : ind_fn f z = 0 :=
begin
  rw [ind_fn_def, extend_apply', pi.zero_apply],
  contrapose h',
  rw not_not at *,
  cases h' with a ha,
  rw ←ha,
  simp,
end

end ind_fn

namespace zmod
lemma embedding_coe {n : ℕ} : embedding (coe : (zmod n)ˣ → zmod n) :=
{ induced := (top_eq_iff_cont_inv.2 (begin convert continuous_of_discrete_topology,
    apply discrete_topology_induced,
    change function.injective coe,
    exact units.ext, end)).symm,
  inj := units.ext, }

lemma open_embedding_coe {n : ℕ} : open_embedding (coe : (zmod n)ˣ → zmod n) :=
⟨embedding_coe, (is_open_coe' _).is_open_range⟩
end zmod

namespace ind_fn
lemma helper_is_loc_const {s : set A} (hs : ¬ (0 : A) ∈ s)
  (f : locally_constant (units (zmod d) × units ℤ_[p]) A) : is_open (ind_fn f ⁻¹' s) :=
begin
  have f1 := locally_constant.is_locally_constant f s,
  conv at f1 { congr, rw [to_fun_eq_coe, ind_fn_eq_fun f], },
  rw set.preimage_comp at f1,
  refine (open_embedding.open_iff_preimage_open (open_embedding.prod zmod.open_embedding_coe
      padic_int.open_embedding_coe) (λ z hz, _)).2 f1,
  by_cases h' : is_unit z.1 ∧ is_unit z.2,
  { refine ⟨(is_unit.unit h'.1, is_unit.unit h'.2), prod.ext_iff.2 _⟩,
    simp only [prod.map_mk],
    refine ⟨is_unit.unit_spec _, is_unit.unit_spec _⟩, },
  { exfalso,
    rw [set.mem_preimage, map_ind_fn_eq_zero f h'] at hz,
    refine hs hz, },
end

lemma preimage_zero_of_loc_const (f : locally_constant ((zmod d)ˣ × ℤ_[p]ˣ) A) : (ind_fn f)⁻¹' {0} =
  (prod.map (coe : units (zmod d) → zmod d) (coe : units ℤ_[p] → ℤ_[p]))'' (f⁻¹' {0}) ∪
  (set.range (prod.map (coe : units (zmod d) → zmod d) (coe : units ℤ_[p] → ℤ_[p])))ᶜ :=
begin
  ext y,
  rw [set.mem_union, set.mem_preimage, set.mem_singleton_iff],
  refine ⟨λ h', _, λ h', _⟩,
  { by_cases h'' : is_unit y.fst ∧ is_unit y.snd,
    { refine or.inl ⟨(is_unit.unit h''.1, is_unit.unit h''.2), _, prod.ext_iff.2
        ⟨is_unit.unit_spec h''.1, is_unit.unit_spec h''.2⟩⟩,
      rw [set.mem_preimage, set.mem_singleton_iff, ←h', map_ind_fn_eq_fn f h''], },
    { right,
      contrapose h'',
      rw [←set.mem_compl_iff, compl_compl, set.mem_range] at h'',
      cases h'' with z hz,
      rw [prod.ext_iff, prod_map] at hz,
      rw [not_not, ←hz.1, ←hz.2],
      refine ⟨units.is_unit z.fst, units.is_unit z.snd⟩, }, },
  { cases h',
    { cases h' with z hz,
      rw [←hz.2, map_ind_fn_eq_fn' f],
      refine hz.1, },
    { apply map_ind_fn_eq_zero,
      refine (λ h, set.not_mem_compl_iff.2 h' _),
      simp only [compl_compl, set.range_prod_map, set.mem_prod, set.mem_range],
      refine ⟨⟨is_unit.unit h.1, is_unit.unit_spec _⟩,
        ⟨is_unit.unit h.2, is_unit.unit_spec _⟩⟩, }, },
end

lemma is_loc_const_ind_fn (f : locally_constant (units (zmod d) × units ℤ_[p]) A) :
  is_locally_constant (ind_fn f) := λ s, begin
  by_cases (0 : A) ∈ s,
  { rw [←set.diff_union_of_subset (set.singleton_subset_iff.2 h), set.preimage_union],
    apply is_open.union,
    { apply helper_is_loc_const _ f,
      simp only [set.mem_diff, set.mem_singleton, not_true, and_false, not_false_iff], },
    { rw preimage_zero_of_loc_const f,
      apply is_open.union ((is_open_map.prod (is_open_coe' _) is_open_coe) _
        (locally_constant.is_locally_constant f _)),
      { rw [is_open_compl_iff, set.range_prod_map],
        refine is_closed.prod (is_closed_discrete (set.range coe)) is_closed_coe, }, }, },
  { apply helper_is_loc_const h f, }, end

lemma add (f1 f2 : (zmod d)ˣ × ℤ_[p]ˣ → R) : ind_fn (f1 + f2) = ind_fn f1 + ind_fn f2 :=
by { rw [ind_fn_def, (add_zero (0 : zmod d × ℤ_[p] → R)).symm, extend_add], }

@[to_additive]
lemma mul (f g : (zmod d)ˣ × ℤ_[p]ˣ → R) : ind_fn (f * g) = ind_fn f * ind_fn g :=
by { rw [ind_fn_def, (mul_zero (0 : zmod d × ℤ_[p] → R)).symm, extend_mul], }

lemma smul (m : R) (f : (zmod d)ˣ × ℤ_[p]ˣ → R) :
  ind_fn (m • f) = m • (ind_fn f) := by { rw [ind_fn_def, (smul_zero m).symm, extend_smul], }
end ind_fn

open ind_fn
/-- The locally constant version of `ind_fn` -/
noncomputable def loc_const_ind_fn (f : locally_constant ((zmod d)ˣ × ℤ_[p]ˣ) A) :
  locally_constant (zmod d × ℤ_[p]) A :=
{ to_fun := ind_fn f,
  is_locally_constant := is_loc_const_ind_fn f }

namespace loc_const_ind_fn
@[simp]
lemma loc_const_ind_fn_def (f : locally_constant ((zmod d)ˣ × ℤ_[p]ˣ) R)
  (x : ((zmod d) × ℤ_[p])) : loc_const_ind_fn f x = ind_fn f x := rfl

lemma add (f1 f2 : locally_constant ((zmod d)ˣ × ℤ_[p]ˣ) R) :
  loc_const_ind_fn (f1 + f2) = loc_const_ind_fn f1 + loc_const_ind_fn f2 :=
by { ext, simp [add R f1 f2], }

@[to_additive]
lemma mul (f g : locally_constant ((zmod d)ˣ × ℤ_[p]ˣ) R) :
  loc_const_ind_fn (f * g) = loc_const_ind_fn f * loc_const_ind_fn g :=
by { ext, simp [mul R f g], }

lemma smul (m : R) (f : locally_constant ((zmod d)ˣ × ℤ_[p]ˣ) R) :
  loc_const_ind_fn (m • f) = m • (loc_const_ind_fn f) := by { ext, simp [smul R m f], }
end loc_const_ind_fn


/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import data.multiset.powerset
import data.multiset.range

/-!
# The `nodup` predicate for multisets without duplicate elements.
-/

namespace multiset
open list

variables {α β γ : Type*}

/- nodup -/

/-- `nodup s` means that `s` has no duplicates, i.e. the multiplicity of
  any element is at most 1. -/
def nodup (s : multiset α) : Prop :=
quot.lift_on s nodup (λ s t p, propext p.nodup_iff)

@[simp] theorem coe_nodup {l : list α} : @nodup α l ↔ l.nodup := iff.rfl

@[simp] theorem nodup_zero : @nodup α 0 := pairwise.nil

@[simp] theorem nodup_cons {a : α} {s : multiset α} : nodup (a ::ₘ s) ↔ a ∉ s ∧ nodup s :=
quot.induction_on s $ λ l, nodup_cons

theorem nodup_cons_of_nodup {a : α} {s : multiset α} (m : a ∉ s) (n : nodup s) : nodup (a ::ₘ s) :=
nodup_cons.2 ⟨m, n⟩

theorem nodup_singleton : ∀ a : α, nodup (a ::ₘ 0) := nodup_singleton

theorem nodup_of_nodup_cons {a : α} {s : multiset α} (h : nodup (a ::ₘ s)) : nodup s :=
(nodup_cons.1 h).2

theorem not_mem_of_nodup_cons {a : α} {s : multiset α} (h : nodup (a ::ₘ s)) : a ∉ s :=
(nodup_cons.1 h).1

theorem nodup_of_le {s t : multiset α} (h : s ≤ t) : nodup t → nodup s :=
le_induction_on h $ λ l₁ l₂, nodup_of_sublist

theorem not_nodup_pair : ∀ a : α, ¬ nodup (a ::ₘ a ::ₘ 0) := not_nodup_pair

theorem nodup_iff_le {s : multiset α} : nodup s ↔ ∀ a : α, ¬ a ::ₘ a ::ₘ 0 ≤ s :=
quot.induction_on s $ λ l, nodup_iff_sublist.trans $ forall_congr $ λ a,
not_congr (@repeat_le_coe _ a 2 _).symm

lemma nodup_iff_ne_cons_cons {s : multiset α} : s.nodup ↔ ∀ a t, s ≠ a ::ₘ a ::ₘ t :=
nodup_iff_le.trans
  ⟨λ h a t s_eq, h a (s_eq.symm ▸ cons_le_cons a (cons_le_cons a (zero_le _))),
   λ h a le, let ⟨t, s_eq⟩ := le_iff_exists_add.mp le in
     h a t (by rwa [cons_add, cons_add, zero_add] at s_eq )⟩

theorem nodup_iff_count_le_one [decidable_eq α] {s : multiset α} : nodup s ↔ ∀ a, count a s ≤ 1 :=
quot.induction_on s $ λ l, nodup_iff_count_le_one

@[simp] theorem count_eq_one_of_mem [decidable_eq α] {a : α} {s : multiset α}
  (d : nodup s) (h : a ∈ s) : count a s = 1 :=
le_antisymm (nodup_iff_count_le_one.1 d a) (count_pos.2 h)

lemma nodup_iff_pairwise {α} {s : multiset α} : nodup s ↔ pairwise (≠) s :=
quotient.induction_on s $ λ l, (pairwise_coe_iff_pairwise (by exact λ a b, ne.symm)).symm

lemma pairwise_of_nodup {r : α → α → Prop} {s : multiset α} :
  (∀a∈s, ∀b∈s, a ≠ b → r a b) → nodup s → pairwise r s :=
quotient.induction_on s $ assume l h hl, ⟨l, rfl, hl.imp_of_mem $ assume a b ha hb, h a ha b hb⟩

lemma forall_of_pairwise {r : α → α → Prop} (H : symmetric r) {s : multiset α}
   (hs : pairwise r s) : (∀a∈s, ∀b∈s, a ≠ b → r a b) :=
let ⟨l, hl₁, hl₂⟩ := hs in hl₁.symm ▸ list.forall_of_pairwise H hl₂

theorem nodup_add {s t : multiset α} : nodup (s + t) ↔ nodup s ∧ nodup t ∧ disjoint s t :=
quotient.induction_on₂ s t $ λ l₁ l₂, nodup_append

theorem disjoint_of_nodup_add {s t : multiset α} (d : nodup (s + t)) : disjoint s t :=
(nodup_add.1 d).2.2

theorem nodup_add_of_nodup {s t : multiset α} (d₁ : nodup s) (d₂ : nodup t) :
  nodup (s + t) ↔ disjoint s t :=
by simp [nodup_add, d₁, d₂]

theorem nodup_of_nodup_map (f : α → β) {s : multiset α} : nodup (map f s) → nodup s :=
quot.induction_on s $ λ l, nodup_of_nodup_map f

theorem nodup_map_on {f : α → β} {s : multiset α} : (∀x∈s, ∀y∈s, f x = f y → x = y) →
  nodup s → nodup (map f s) :=
quot.induction_on s $ λ l, nodup_map_on

theorem nodup_map {f : α → β} {s : multiset α} (hf : function.injective f) :
  nodup s → nodup (map f s) :=
nodup_map_on (λ x _ y _ h, hf h)

theorem inj_on_of_nodup_map {f : α → β} {s : multiset α} :
  nodup (map f s) → ∀ (x ∈ s) (y ∈ s), f x = f y → x = y :=
quot.induction_on s $ λ l, inj_on_of_nodup_map

theorem nodup_map_iff_inj_on {f : α → β} {s : multiset α} (d : nodup s) :
  nodup (map f s) ↔ (∀ (x ∈ s) (y ∈ s), f x = f y → x = y) :=
⟨inj_on_of_nodup_map, λ h, nodup_map_on h d⟩

theorem nodup_filter (p : α → Prop) [decidable_pred p] {s} : nodup s → nodup (filter p s) :=
quot.induction_on s $ λ l, nodup_filter p

@[simp] theorem nodup_attach {s : multiset α} : nodup (attach s) ↔ nodup s :=
quot.induction_on s $ λ l, nodup_attach

theorem nodup_pmap {p : α → Prop} {f : Π a, p a → β} {s : multiset α} {H}
  (hf : ∀ a ha b hb, f a ha = f b hb → a = b) : nodup s → nodup (pmap f s H) :=
quot.induction_on s (λ l H, nodup_pmap hf) H

instance nodup_decidable [decidable_eq α] (s : multiset α) : decidable (nodup s) :=
quotient.rec_on_subsingleton s $ λ l, l.nodup_decidable

theorem nodup_erase_eq_filter [decidable_eq α] (a : α) {s} : nodup s → s.erase a = filter (≠ a) s :=
quot.induction_on s $ λ l d, congr_arg coe $ nodup_erase_eq_filter a d

theorem nodup_erase_of_nodup [decidable_eq α] (a : α) {l} : nodup l → nodup (l.erase a) :=
nodup_of_le (erase_le _ _)

theorem mem_erase_iff_of_nodup [decidable_eq α] {a b : α} {l} (d : nodup l) :
  a ∈ l.erase b ↔ a ≠ b ∧ a ∈ l :=
by rw nodup_erase_eq_filter b d; simp [and_comm]

theorem mem_erase_of_nodup [decidable_eq α] {a : α} {l} (h : nodup l) : a ∉ l.erase a :=
by rw mem_erase_iff_of_nodup h; simp

theorem nodup_product {s : multiset α} {t : multiset β} : nodup s → nodup t → nodup (product s t) :=
quotient.induction_on₂ s t $ λ l₁ l₂ d₁ d₂, by simp [nodup_product d₁ d₂]

theorem nodup_sigma {σ : α → Type*} {s : multiset α} {t : Π a, multiset (σ a)} :
  nodup s → (∀ a, nodup (t a)) → nodup (s.sigma t) :=
quot.induction_on s $ assume l₁,
begin
  choose f hf using assume a, quotient.exists_rep (t a),
  rw show t = λ a, f a, from (eq.symm $ funext $ λ a, hf a),
  simpa using nodup_sigma
end

theorem nodup_filter_map (f : α → option β) {s : multiset α}
  (H : ∀ (a a' : α) (b : β), b ∈ f a → b ∈ f a' → a = a') :
  nodup s → nodup (filter_map f s) :=
quot.induction_on s $ λ l, nodup_filter_map H

theorem nodup_inter_left [decidable_eq α] {s : multiset α} (t) : nodup s → nodup (s ∩ t) :=
nodup_of_le $ inter_le_left _ _

theorem nodup_inter_right [decidable_eq α] (s) {t : multiset α} : nodup t → nodup (s ∩ t) :=
nodup_of_le $ inter_le_right _ _

@[simp] theorem nodup_union [decidable_eq α] {s t : multiset α} :
  nodup (s ∪ t) ↔ nodup s ∧ nodup t :=
⟨λ h, ⟨nodup_of_le (le_union_left _ _) h, nodup_of_le (le_union_right _ _) h⟩,
 λ ⟨h₁, h₂⟩, nodup_iff_count_le_one.2 $ λ a, by rw [count_union]; exact
   max_le (nodup_iff_count_le_one.1 h₁ a) (nodup_iff_count_le_one.1 h₂ a)⟩

@[simp] theorem nodup_powerset {s : multiset α} : nodup (powerset s) ↔ nodup s :=
⟨λ h, nodup_of_nodup_map _ (nodup_of_le (map_single_le_powerset _) h),
  quotient.induction_on s $ λ l h,
  by simp; refine list.nodup_map_on _ (nodup_sublists'.2 h); exact
  λ x sx y sy e,
    (h.sublist_ext (mem_sublists'.1 sx) (mem_sublists'.1 sy)).1
      (quotient.exact e)⟩

theorem nodup_powerset_len {n : ℕ} {s : multiset α}
  (h : nodup s) : nodup (powerset_len n s) :=
nodup_of_le (powerset_len_le_powerset _ _) (nodup_powerset.2 h)

@[simp] lemma nodup_bind {s : multiset α} {t : α → multiset β} :
  nodup (bind s t) ↔ ((∀a∈s, nodup (t a)) ∧ (s.pairwise (λa b, disjoint (t a) (t b)))) :=
have h₁ : ∀a, ∃l:list β, t a = l, from
  assume a, quot.induction_on (t a) $ assume l, ⟨l, rfl⟩,
let ⟨t', h'⟩ := classical.axiom_of_choice h₁ in
have t = λa, t' a, from funext h',
have hd : symmetric (λa b, list.disjoint (t' a) (t' b)), from assume a b h, h.symm,
quot.induction_on s $ by simp [this, list.nodup_bind, pairwise_coe_iff_pairwise hd]

theorem nodup_ext {s t : multiset α} : nodup s → nodup t → (s = t ↔ ∀ a, a ∈ s ↔ a ∈ t) :=
quotient.induction_on₂ s t $ λ l₁ l₂ d₁ d₂, quotient.eq.trans $ perm_ext d₁ d₂

theorem le_iff_subset {s t : multiset α} : nodup s → (s ≤ t ↔ s ⊆ t) :=
quotient.induction_on₂ s t $ λ l₁ l₂ d, ⟨subset_of_le, subperm_of_subset_nodup d⟩

theorem range_le {m n : ℕ} : range m ≤ range n ↔ m ≤ n :=
(le_iff_subset (nodup_range _)).trans range_subset

theorem mem_sub_of_nodup [decidable_eq α] {a : α} {s t : multiset α} (d : nodup s) :
  a ∈ s - t ↔ a ∈ s ∧ a ∉ t :=
⟨λ h, ⟨mem_of_le (sub_le_self _ _) h, λ h',
  by refine count_eq_zero.1 _ h; rw [count_sub a s t, nat.sub_eq_zero_iff_le];
     exact le_trans (nodup_iff_count_le_one.1 d _) (count_pos.2 h')⟩,
 λ ⟨h₁, h₂⟩, or.resolve_right (mem_add.1 $ mem_of_le (le_sub_add _ _) h₁) h₂⟩

lemma map_eq_map_of_bij_of_nodup (f : α → γ) (g : β → γ) {s : multiset α} {t : multiset β}
  (hs : s.nodup) (ht : t.nodup) (i : Πa∈s, β)
  (hi : ∀a ha, i a ha ∈ t) (h : ∀a ha, f a = g (i a ha))
  (i_inj : ∀a₁ a₂ ha₁ ha₂, i a₁ ha₁ = i a₂ ha₂ → a₁ = a₂)
  (i_surj : ∀b∈t, ∃a ha, b = i a ha) :
  s.map f = t.map g :=
have t = s.attach.map (λ x, i x.1 x.2),
  from (nodup_ext ht (nodup_map
      (show function.injective (λ x : {x // x ∈ s}, i x.1 x.2), from λ x y hxy,
        subtype.eq (i_inj x.1 y.1 x.2 y.2 hxy))
      (nodup_attach.2 hs))).2
    (λ x, by simp only [mem_map, true_and, subtype.exists, eq_comm, mem_attach];
      exact ⟨i_surj _, λ ⟨y, hy⟩, hy.snd.symm ▸ hi _ _⟩),
calc s.map f = s.pmap  (λ x _, f x) (λ _, id) : by rw [pmap_eq_map]
... = s.attach.map (λ x, f x.1) : by rw [pmap_eq_map_attach]
... = t.map g : by rw [this, multiset.map_map]; exact map_congr (λ x _, h _ _)

end multiset