/-
Copyright (c) 2022 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/
import algebra.big_operators.fin
import data.finset.nat_antidiagonal
import data.fin.vec_notation
import logic.equiv.fin

/-!
# Collections of tuples of naturals with the same sum

This file generalizes `list.nat.antidiagonal n`, `multiset.nat.antidiagonal n`, and
`finset.nat.antidiagonal n` from the pair of elements `x : ℕ × ℕ` such that `n = x.1 + x.2`, to
the sequence of elements `x : fin k → ℕ` such that `n = ∑ i, x i`.

## Main definitions

* `list.nat.antidiagonal_tuple`
* `multiset.nat.antidiagonal_tuple`
* `finset.nat.antidiagonal_tuple`

## Main results

* `antidiagonal_tuple 2 n` is analogous to `antidiagonal n`:

  * `list.nat.antidiagonal_tuple_two`
  * `multiset.nat.antidiagonal_tuple_two`
  * `finset.nat.antidiagonal_tuple_two`

## Implementation notes

While we could implement this by filtering `(fintype.pi_finset $ λ _, range (n + 1))` or similar,
this implementation would be much slower.

In the future, we could consider generalizing `finset.nat.antidiagonal_tuple` further to
support finitely-supported functions, as is done with `cut` in
`archive/100-theorems-list/45_partition.lean`.
-/

open_locale big_operators

/-! ### Lists -/

namespace list.nat

/-- `list.antidiagonal_tuple k n` is a list of all `k`-tuples which sum to `n`.

This list contains no duplicates (`list.nat.nodup_antidiagonal_tuple`), and is sorted
lexicographically (`list.nat.antidiagonal_tuple_pairwise_pi_lex`), starting with `![0, ..., n]`
and ending with `![n, ..., 0]`.

```
#eval antidiagonal_tuple 3 2
-- [![0, 0, 2], ![0, 1, 1], ![0, 2, 0], ![1, 0, 1], ![1, 1, 0], ![2, 0, 0]]
```
-/
def antidiagonal_tuple : Π k, ℕ → list (fin k → ℕ)
| 0 0 := [![]]
| 0 (n + 1) := []
| (k + 1) n := (list.nat.antidiagonal n).bind $ λ ni,
  (antidiagonal_tuple k ni.2).map $ λ x, fin.cons (ni.1) x

@[simp] lemma antidiagonal_tuple_zero_zero : antidiagonal_tuple 0 0 = [![]] := rfl
@[simp] lemma antidiagonal_tuple_zero_succ (n : ℕ) : antidiagonal_tuple 0 n.succ = [] := rfl

lemma mem_antidiagonal_tuple {n : ℕ} {k : ℕ} {x : fin k → ℕ} :
  x ∈ antidiagonal_tuple k n ↔ ∑ i, x i = n :=
begin
  induction k with k ih generalizing n,
  { cases n,
    { simp },
    { simp [eq_comm] }, },
  { refine fin.cons_induction (λ x₀ x, _) x,
    simp_rw [fin.sum_cons, antidiagonal_tuple, list.mem_bind, list.mem_map,
      list.nat.mem_antidiagonal, fin.cons_eq_cons, exists_eq_right_right, ih, prod.exists],
    split,
    { rintros ⟨a, b, rfl, rfl, rfl⟩, refl },
    { rintro rfl, exact ⟨_, _, rfl, rfl, rfl⟩, } },
end

/-- The antidiagonal of `n` does not contain duplicate entries. -/
lemma nodup_antidiagonal_tuple (k n : ℕ) : list.nodup (antidiagonal_tuple k n) :=
begin
  induction k with k ih generalizing n,
  { cases n,
    { simp },
    { simp [eq_comm] }, },
  simp_rw [antidiagonal_tuple, list.nodup_bind],
  split,
  { intros i hi,
    exact (ih i.snd).map (fin.cons_right_injective (i.fst : (λ _, ℕ) 0)), },
  induction n,
  { exact list.pairwise_singleton _ _ },
  { rw list.nat.antidiagonal_succ,
    refine list.pairwise.cons (λ a ha x hx₁ hx₂, _) (n_ih.map _ (λ a b h x hx₁ hx₂, _)),
    { rw list.mem_map at hx₁ hx₂ ha,
      obtain ⟨⟨a, -, rfl⟩, ⟨x₁, -, rfl⟩, ⟨x₂, -, h⟩⟩ := ⟨ha, hx₁, hx₂⟩,
      rw fin.cons_eq_cons at h,
      injection h.1, },
    { rw list.mem_map at hx₁ hx₂,
      obtain ⟨⟨x₁, hx₁, rfl⟩, ⟨x₂, hx₂, h₁₂⟩⟩ := ⟨hx₁, hx₂⟩,
      dsimp at h₁₂,
      rw [fin.cons_eq_cons, nat.succ_inj'] at h₁₂,
      obtain ⟨h₁₂, rfl⟩ := h₁₂,
      rw h₁₂ at h,
      exact h (list.mem_map_of_mem _ hx₁) (list.mem_map_of_mem _ hx₂) }, },
end

lemma antidiagonal_tuple_zero_right : ∀ k, antidiagonal_tuple k 0 = [0]
| 0 := congr_arg (λ x, [x]) $ subsingleton.elim _ _
| (k + 1) := begin
  rw [antidiagonal_tuple, antidiagonal_zero, list.bind_singleton, antidiagonal_tuple_zero_right k,
    list.map_singleton],
  exact congr_arg (λ x, [x]) matrix.cons_zero_zero
end

@[simp] lemma antidiagonal_tuple_one (n : ℕ) : antidiagonal_tuple 1 n = [![n]] :=
begin
  simp_rw [antidiagonal_tuple, antidiagonal, list.range_succ, list.map_append, list.map_singleton,
    tsub_self, list.bind_append, list.bind_singleton, antidiagonal_tuple_zero_zero,
    list.map_singleton, list.map_bind],
  conv_rhs { rw ← list.nil_append [![n]]},
  congr' 1,
  simp_rw [list.bind_eq_nil, list.mem_range, list.map_eq_nil],
  intros x hx,
  obtain ⟨m, rfl⟩ := nat.exists_eq_add_of_lt hx,
  rw [add_assoc, add_tsub_cancel_left, antidiagonal_tuple_zero_succ],
end

lemma antidiagonal_tuple_two (n : ℕ) :
  antidiagonal_tuple 2 n = (antidiagonal n).map (λ i, ![i.1, i.2]) :=
begin
  rw antidiagonal_tuple,
  simp_rw [antidiagonal_tuple_one, list.map_singleton],
  rw [list.map_eq_bind],
  refl,
end

lemma antidiagonal_tuple_pairwise_pi_lex : ∀ k n,
  (antidiagonal_tuple k n).pairwise (pi.lex (<) (λ _, (<)))
| 0 0 := list.pairwise_singleton _ _
| 0 (n + 1) := list.pairwise.nil
| (k + 1) n := begin
  simp_rw [antidiagonal_tuple, list.pairwise_bind, list.pairwise_map, list.mem_map,
    forall_exists_index, and_imp, forall_apply_eq_imp_iff₂],
  simp only [mem_antidiagonal, prod.forall, and_imp, forall_apply_eq_imp_iff₂],
  simp only [fin.pi_lex_lt_cons_cons, eq_self_iff_true, true_and, lt_self_iff_false, false_or],
  refine ⟨λ _ _ _, antidiagonal_tuple_pairwise_pi_lex k _, _⟩,
  induction n,
  { rw [antidiagonal_zero],
    exact list.pairwise_singleton _ _ },
  { rw [antidiagonal_succ, list.pairwise_cons, list.pairwise_map],
    refine ⟨λ p hp x hx y hy, _, _⟩,
    { rw [list.mem_map, prod.exists] at hp,
      obtain ⟨a, b, hab, (rfl : (nat.succ a, b) = p)⟩ := hp,
      exact or.inl (nat.zero_lt_succ _), },
    dsimp,
    simp_rw [nat.succ_inj', nat.succ_lt_succ_iff],
    exact n_ih },
end

end list.nat

/-! ### Multisets -/
namespace multiset.nat

/-- `multiset.antidiagonal_tuple k n` is a multiset of `k`-tuples summing to `n` -/
def antidiagonal_tuple (k n : ℕ) : multiset (fin k → ℕ) :=
list.nat.antidiagonal_tuple k n

@[simp] lemma antidiagonal_tuple_zero_zero : antidiagonal_tuple 0 0 = { ![]} := rfl
@[simp] lemma antidiagonal_tuple_zero_succ (n : ℕ) : antidiagonal_tuple 0 n.succ = 0 := rfl

lemma mem_antidiagonal_tuple {n : ℕ} {k : ℕ} {x : fin k → ℕ} :
  x ∈ antidiagonal_tuple k n ↔ ∑ i, x i = n :=
list.nat.mem_antidiagonal_tuple

lemma nodup_antidiagonal_tuple (k n : ℕ) : (antidiagonal_tuple k n).nodup :=
list.nat.nodup_antidiagonal_tuple _ _

lemma antidiagonal_tuple_zero_right (k : ℕ) : antidiagonal_tuple k 0 = {0} :=
congr_arg _ (list.nat.antidiagonal_tuple_zero_right k)

@[simp] lemma antidiagonal_tuple_one (n : ℕ) : antidiagonal_tuple 1 n = { ![n]} :=
congr_arg _ (list.nat.antidiagonal_tuple_one n)

lemma antidiagonal_tuple_two (n : ℕ) :
  antidiagonal_tuple 2 n = (antidiagonal n).map (λ i, ![i.1, i.2]) :=
congr_arg _ (list.nat.antidiagonal_tuple_two n)

end multiset.nat

/-! ### Finsets -/
namespace finset.nat

/-- `finset.antidiagonal_tuple k n` is a finset of `k`-tuples summing to `n` -/
def antidiagonal_tuple (k n : ℕ) : finset (fin k → ℕ) :=
⟨multiset.nat.antidiagonal_tuple k n, multiset.nat.nodup_antidiagonal_tuple k n⟩

@[simp] lemma antidiagonal_tuple_zero_zero : antidiagonal_tuple 0 0 = { ![]} := rfl
@[simp] lemma antidiagonal_tuple_zero_succ (n : ℕ) : antidiagonal_tuple 0 n.succ = ∅ := rfl

lemma mem_antidiagonal_tuple {n : ℕ} {k : ℕ} {x : fin k → ℕ} :
  x ∈ antidiagonal_tuple k n ↔ ∑ i, x i = n :=
list.nat.mem_antidiagonal_tuple

lemma antidiagonal_tuple_zero_right (k : ℕ) : antidiagonal_tuple k 0 = {0} :=
finset.eq_of_veq (multiset.nat.antidiagonal_tuple_zero_right k)

@[simp] lemma antidiagonal_tuple_one (n : ℕ) : antidiagonal_tuple 1 n = { ![n]} :=
finset.eq_of_veq (multiset.nat.antidiagonal_tuple_one n)

lemma antidiagonal_tuple_two (n : ℕ) :
  antidiagonal_tuple 2 n = (antidiagonal n).map (pi_fin_two_equiv (λ _, ℕ)).symm.to_embedding :=
finset.eq_of_veq (multiset.nat.antidiagonal_tuple_two n)

section equiv_prod

/-- The disjoint union of antidiagonal tuples `Σ n, antidiagonal_tuple k n` is equivalent to the
`k`-tuple `fin k → ℕ`. This is such an equivalence, obtained by mapping `(n, x)` to `x`.

This is the tuple version of `finset.nat.sigma_antidiagonal_equiv_prod`. -/
@[simps] def sigma_antidiagonal_tuple_equiv_tuple (k : ℕ) :
  (Σ n, antidiagonal_tuple k n) ≃ (fin k → ℕ) :=
{ to_fun := λ x, x.2,
  inv_fun := λ x, ⟨∑ i, x i, x, mem_antidiagonal_tuple.mpr rfl⟩,
  left_inv := λ ⟨n, t, h⟩, sigma.subtype_ext (mem_antidiagonal_tuple.mp h) rfl,
  right_inv := λ x, rfl }

end equiv_prod

end finset.nat


-- ---------------------------------------------------------------------
-- Ejercicio. Declarar α como una variables de tipos habitados.
-- ----------------------------------------------------------------------

variables {α : Type*} [inhabited α]

-- ---------------------------------------------------------------------
-- Ejercicio. Calcular el tipo de
--     default α
-- ----------------------------------------------------------------------

-- #check default α

-- Comntario: Al colocar el cursor sobre check se obtiene
--    default α : α

-- ---------------------------------------------------------------------
-- Ejercicio. Declarar P como un predicado sobre α tal que existe algún
-- elemento que verifica P.
-- ----------------------------------------------------------------------

variables (P : α → Prop) (h : ∃ x, P x)

-- ---------------------------------------------------------------------
-- Ejercicio. Calcular el tipo de
--    classical.some h
-- ----------------------------------------------------------------------

-- #check classical.some h

-- Comntario: Al colocar el cursor sobre check se obtiene
--    classical.some h : α

-- ---------------------------------------------------------------------
-- Ejercicio. Demostrar que
--    P (classical.some h)
-- ----------------------------------------------------------------------

example : P (classical.some h) :=
classical.some_spec h


/-
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 ring_theory.polynomial.pochhammer
! leanprover-community/mathlib commit 69c6a5a12d8a2b159f20933e60115a4f2de62b58
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Tactic.Abel
import Mathbin.Data.Polynomial.Eval

/-!
# The Pochhammer polynomials

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

We define and prove some basic relations about
`pochhammer S n : S[X] := X * (X + 1) * ... * (X + n - 1)`
which is also known as the rising factorial. A version of this definition
that is focused on `nat` can be found in `data.nat.factorial` as `nat.asc_factorial`.

## Implementation

As with many other families of polynomials, even though the coefficients are always in `ℕ`,
we define the polynomial with coefficients in any `[semiring S]`.

## TODO

There is lots more in this direction:
* q-factorials, q-binomials, q-Pochhammer.
-/

universe u v

open Polynomial

open Polynomial

section Semiring

variable (S : Type u) [Semiring S]

#print pochhammer /-
/-- `pochhammer S n` is the polynomial `X * (X+1) * ... * (X + n - 1)`,
with coefficients in the semiring `S`.
-/
noncomputable def pochhammer : ℕ → S[X]
  | 0 => 1
  | n + 1 => X * (pochhammer n).comp (X + 1)
#align pochhammer pochhammer
-/

#print pochhammer_zero /-
@[simp]
theorem pochhammer_zero : pochhammer S 0 = 1 :=
  rfl
#align pochhammer_zero pochhammer_zero
-/

#print pochhammer_one /-
@[simp]
theorem pochhammer_one : pochhammer S 1 = X := by simp [pochhammer]
#align pochhammer_one pochhammer_one
-/

#print pochhammer_succ_left /-
theorem pochhammer_succ_left (n : ℕ) : pochhammer S (n + 1) = X * (pochhammer S n).comp (X + 1) :=
  by rw [pochhammer]
#align pochhammer_succ_left pochhammer_succ_left
-/

section

variable {S} {T : Type v} [Semiring T]

#print pochhammer_map /-
@[simp]
theorem pochhammer_map (f : S →+* T) (n : ℕ) : (pochhammer S n).map f = pochhammer T n :=
  by
  induction' n with n ih
  · simp
  · simp [ih, pochhammer_succ_left, map_comp]
#align pochhammer_map pochhammer_map
-/

end

#print pochhammer_eval_cast /-
@[simp, norm_cast]
theorem pochhammer_eval_cast (n k : ℕ) : ((pochhammer ℕ n).eval k : S) = (pochhammer S n).eval k :=
  by
  rw [← pochhammer_map (algebraMap ℕ S), eval_map, ← eq_natCast (algebraMap ℕ S), eval₂_at_nat_cast,
    Nat.cast_id, eq_natCast]
#align pochhammer_eval_cast pochhammer_eval_cast
-/

/- warning: pochhammer_eval_zero -> pochhammer_eval_zero is a dubious translation:
lean 3 declaration is
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S] {n : Nat}, Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_1 (OfNat.ofNat.{u1} S 0 (OfNat.mk.{u1} S 0 (Zero.zero.{u1} S (MulZeroClass.toHasZero.{u1} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) (pochhammer.{u1} S _inst_1 n)) (ite.{succ u1} S (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) (Nat.decidableEq n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) (OfNat.ofNat.{u1} S 1 (OfNat.mk.{u1} S 1 (One.one.{u1} S (AddMonoidWithOne.toOne.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) (OfNat.ofNat.{u1} S 0 (OfNat.mk.{u1} S 0 (Zero.zero.{u1} S (MulZeroClass.toHasZero.{u1} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))))
but is expected to have type
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S] {n : Nat}, Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_1 (OfNat.ofNat.{u1} S 0 (Zero.toOfNat0.{u1} S (MonoidWithZero.toZero.{u1} S (Semiring.toMonoidWithZero.{u1} S _inst_1)))) (pochhammer.{u1} S _inst_1 n)) (ite.{succ u1} S (Eq.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) (instDecidableEqNat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) (OfNat.ofNat.{u1} S 1 (One.toOfNat1.{u1} S (Semiring.toOne.{u1} S _inst_1))) (OfNat.ofNat.{u1} S 0 (Zero.toOfNat0.{u1} S (MonoidWithZero.toZero.{u1} S (Semiring.toMonoidWithZero.{u1} S _inst_1)))))
Case conversion may be inaccurate. Consider using '#align pochhammer_eval_zero pochhammer_eval_zeroₓ'. -/
theorem pochhammer_eval_zero {n : ℕ} : (pochhammer S n).eval 0 = if n = 0 then 1 else 0 :=
  by
  cases n
  · simp
  · simp [X_mul, Nat.succ_ne_zero, pochhammer_succ_left]
#align pochhammer_eval_zero pochhammer_eval_zero

/- warning: pochhammer_zero_eval_zero -> pochhammer_zero_eval_zero is a dubious translation:
lean 3 declaration is
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S], Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_1 (OfNat.ofNat.{u1} S 0 (OfNat.mk.{u1} S 0 (Zero.zero.{u1} S (MulZeroClass.toHasZero.{u1} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) (pochhammer.{u1} S _inst_1 (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))))) (OfNat.ofNat.{u1} S 1 (OfNat.mk.{u1} S 1 (One.one.{u1} S (AddMonoidWithOne.toOne.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1)))))))
but is expected to have type
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S], Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_1 (OfNat.ofNat.{u1} S 0 (Zero.toOfNat0.{u1} S (MonoidWithZero.toZero.{u1} S (Semiring.toMonoidWithZero.{u1} S _inst_1)))) (pochhammer.{u1} S _inst_1 (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)))) (OfNat.ofNat.{u1} S 1 (One.toOfNat1.{u1} S (Semiring.toOne.{u1} S _inst_1)))
Case conversion may be inaccurate. Consider using '#align pochhammer_zero_eval_zero pochhammer_zero_eval_zeroₓ'. -/
theorem pochhammer_zero_eval_zero : (pochhammer S 0).eval 0 = 1 := by simp
#align pochhammer_zero_eval_zero pochhammer_zero_eval_zero

/- warning: pochhammer_ne_zero_eval_zero -> pochhammer_ne_zero_eval_zero is a dubious translation:
lean 3 declaration is
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S] {n : Nat}, (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) -> (Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_1 (OfNat.ofNat.{u1} S 0 (OfNat.mk.{u1} S 0 (Zero.zero.{u1} S (MulZeroClass.toHasZero.{u1} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) (pochhammer.{u1} S _inst_1 n)) (OfNat.ofNat.{u1} S 0 (OfNat.mk.{u1} S 0 (Zero.zero.{u1} S (MulZeroClass.toHasZero.{u1} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))))
but is expected to have type
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S] {n : Nat}, (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) -> (Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_1 (OfNat.ofNat.{u1} S 0 (Zero.toOfNat0.{u1} S (MonoidWithZero.toZero.{u1} S (Semiring.toMonoidWithZero.{u1} S _inst_1)))) (pochhammer.{u1} S _inst_1 n)) (OfNat.ofNat.{u1} S 0 (Zero.toOfNat0.{u1} S (MonoidWithZero.toZero.{u1} S (Semiring.toMonoidWithZero.{u1} S _inst_1)))))
Case conversion may be inaccurate. Consider using '#align pochhammer_ne_zero_eval_zero pochhammer_ne_zero_eval_zeroₓ'. -/
@[simp]
theorem pochhammer_ne_zero_eval_zero {n : ℕ} (h : n ≠ 0) : (pochhammer S n).eval 0 = 0 := by
  simp [pochhammer_eval_zero, h]
#align pochhammer_ne_zero_eval_zero pochhammer_ne_zero_eval_zero

#print pochhammer_succ_right /-
theorem pochhammer_succ_right (n : ℕ) : pochhammer S (n + 1) = pochhammer S n * (X + n) :=
  by
  suffices h : pochhammer ℕ (n + 1) = pochhammer ℕ n * (X + n)
  · apply_fun Polynomial.map (algebraMap ℕ S)  at h
    simpa only [pochhammer_map, Polynomial.map_mul, Polynomial.map_add, map_X,
      Polynomial.map_nat_cast] using h
  induction' n with n ih
  · simp
  ·
    conv_lhs =>
      rw [pochhammer_succ_left, ih, mul_comp, ← mul_assoc, ← pochhammer_succ_left, add_comp, X_comp,
        nat_cast_comp, add_assoc, add_comm (1 : ℕ[X]), ← Nat.cast_succ]
#align pochhammer_succ_right pochhammer_succ_right
-/

/- warning: pochhammer_succ_eval -> pochhammer_succ_eval is a dubious translation:
lean 3 declaration is
  forall {S : Type.{u1}} [_inst_2 : Semiring.{u1} S] (n : Nat) (k : S), Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_2 k (pochhammer.{u1} S _inst_2 (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (Distrib.toHasMul.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))) (Polynomial.eval.{u1} S _inst_2 k (pochhammer.{u1} S _inst_2 n)) (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toHasAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))) k ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))))) n)))
but is expected to have type
  forall {S : Type.{u1}} [_inst_2 : Semiring.{u1} S] (n : Nat) (k : S), Eq.{succ u1} S (Polynomial.eval.{u1} S _inst_2 k (pochhammer.{u1} S _inst_2 (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))) (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (NonUnitalNonAssocSemiring.toMul.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2)))) (Polynomial.eval.{u1} S _inst_2 k (pochhammer.{u1} S _inst_2 n)) (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))) k (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_2) n)))
Case conversion may be inaccurate. Consider using '#align pochhammer_succ_eval pochhammer_succ_evalₓ'. -/
theorem pochhammer_succ_eval {S : Type _} [Semiring S] (n : ℕ) (k : S) :
    (pochhammer S (n + 1)).eval k = (pochhammer S n).eval k * (k + n) := by
  rw [pochhammer_succ_right, mul_add, eval_add, eval_mul_X, ← Nat.cast_comm, ← C_eq_nat_cast,
    eval_C_mul, Nat.cast_comm, ← mul_add]
#align pochhammer_succ_eval pochhammer_succ_eval

#print pochhammer_succ_comp_x_add_one /-
theorem pochhammer_succ_comp_x_add_one (n : ℕ) :
    (pochhammer S (n + 1)).comp (X + 1) =
      pochhammer S (n + 1) + (n + 1) • (pochhammer S n).comp (X + 1) :=
  by
  suffices
    (pochhammer ℕ (n + 1)).comp (X + 1) =
      pochhammer ℕ (n + 1) + (n + 1) * (pochhammer ℕ n).comp (X + 1)
    by simpa [map_comp] using congr_arg (Polynomial.map (Nat.castRingHom S)) this
  nth_rw 2 [pochhammer_succ_left]
  rw [← add_mul, pochhammer_succ_right ℕ n, mul_comp, mul_comm, add_comp, X_comp, nat_cast_comp,
    add_comm ↑n, ← add_assoc]
#align pochhammer_succ_comp_X_add_one pochhammer_succ_comp_x_add_one
-/

#print Polynomial.mul_x_add_nat_cast_comp /-
theorem Polynomial.mul_x_add_nat_cast_comp {p q : S[X]} {n : ℕ} :
    (p * (X + n)).comp q = p.comp q * (q + n) := by
  rw [mul_add, add_comp, mul_X_comp, ← Nat.cast_comm, nat_cast_mul_comp, Nat.cast_comm, mul_add]
#align polynomial.mul_X_add_nat_cast_comp Polynomial.mul_x_add_nat_cast_comp
-/

#print pochhammer_mul /-
theorem pochhammer_mul (n m : ℕ) :
    pochhammer S n * (pochhammer S m).comp (X + n) = pochhammer S (n + m) :=
  by
  induction' m with m ih
  · simp
  ·
    rw [pochhammer_succ_right, Polynomial.mul_x_add_nat_cast_comp, ← mul_assoc, ih,
      Nat.succ_eq_add_one, ← add_assoc, pochhammer_succ_right, Nat.cast_add, add_assoc]
#align pochhammer_mul pochhammer_mul
-/

#print pochhammer_nat_eq_ascFactorial /-
theorem pochhammer_nat_eq_ascFactorial (n : ℕ) :
    ∀ k, (pochhammer ℕ k).eval (n + 1) = n.ascFactorial k
  | 0 => by erw [eval_one] <;> rfl
  | t + 1 => by
    rw [pochhammer_succ_right, eval_mul, pochhammer_nat_eq_ascFactorial t]
    suffices n.asc_factorial t * (n + 1 + t) = n.asc_factorial (t + 1) by simpa
    rw [Nat.ascFactorial_succ, add_right_comm, mul_comm]
#align pochhammer_nat_eq_asc_factorial pochhammer_nat_eq_ascFactorial
-/

#print pochhammer_nat_eq_descFactorial /-
theorem pochhammer_nat_eq_descFactorial (a b : ℕ) :
    (pochhammer ℕ b).eval a = (a + b - 1).descFactorial b :=
  by
  cases b
  · rw [Nat.descFactorial_zero, pochhammer_zero, Polynomial.eval_one]
  rw [Nat.add_succ, Nat.succ_sub_succ, tsub_zero]
  cases a
  ·
    rw [pochhammer_ne_zero_eval_zero _ b.succ_ne_zero, zero_add,
      Nat.descFactorial_of_lt b.lt_succ_self]
  ·
    rw [Nat.succ_add, ← Nat.add_succ, Nat.add_descFactorial_eq_ascFactorial,
      pochhammer_nat_eq_ascFactorial]
#align pochhammer_nat_eq_desc_factorial pochhammer_nat_eq_descFactorial
-/

end Semiring

section StrictOrderedSemiring

variable {S : Type _} [StrictOrderedSemiring S]

/- warning: pochhammer_pos -> pochhammer_pos is a dubious translation:
lean 3 declaration is
  forall {S : Type.{u1}} [_inst_1 : StrictOrderedSemiring.{u1} S] (n : Nat) (s : S), (LT.lt.{u1} S (Preorder.toLT.{u1} S (PartialOrder.toPreorder.{u1} S (OrderedCancelAddCommMonoid.toPartialOrder.{u1} S (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{u1} S _inst_1)))) (OfNat.ofNat.{u1} S 0 (OfNat.mk.{u1} S 0 (Zero.zero.{u1} S (MulZeroClass.toHasZero.{u1} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1)))))))) s) -> (LT.lt.{u1} S (Preorder.toLT.{u1} S (PartialOrder.toPreorder.{u1} S (OrderedCancelAddCommMonoid.toPartialOrder.{u1} S (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{u1} S _inst_1)))) (OfNat.ofNat.{u1} S 0 (OfNat.mk.{u1} S 0 (Zero.zero.{u1} S (MulZeroClass.toHasZero.{u1} S (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1)))))))) (Polynomial.eval.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1) s (pochhammer.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1) n)))
but is expected to have type
  forall {S : Type.{u1}} [_inst_1 : StrictOrderedSemiring.{u1} S] (n : Nat) (s : S), (LT.lt.{u1} S (Preorder.toLT.{u1} S (PartialOrder.toPreorder.{u1} S (StrictOrderedSemiring.toPartialOrder.{u1} S _inst_1))) (OfNat.ofNat.{u1} S 0 (Zero.toOfNat0.{u1} S (MonoidWithZero.toZero.{u1} S (Semiring.toMonoidWithZero.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1))))) s) -> (LT.lt.{u1} S (Preorder.toLT.{u1} S (PartialOrder.toPreorder.{u1} S (StrictOrderedSemiring.toPartialOrder.{u1} S _inst_1))) (OfNat.ofNat.{u1} S 0 (Zero.toOfNat0.{u1} S (MonoidWithZero.toZero.{u1} S (Semiring.toMonoidWithZero.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1))))) (Polynomial.eval.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1) s (pochhammer.{u1} S (StrictOrderedSemiring.toSemiring.{u1} S _inst_1) n)))
Case conversion may be inaccurate. Consider using '#align pochhammer_pos pochhammer_posₓ'. -/
theorem pochhammer_pos (n : ℕ) (s : S) (h : 0 < s) : 0 < (pochhammer S n).eval s :=
  by
  induction' n with n ih
  · simp only [Nat.zero_eq, pochhammer_zero, eval_one]
    exact zero_lt_one
  · rw [pochhammer_succ_right, mul_add, eval_add, ← Nat.cast_comm, eval_nat_cast_mul, eval_mul_X,
      Nat.cast_comm, ← mul_add]
    exact mul_pos ih (lt_of_lt_of_le h ((le_add_iff_nonneg_right _).mpr (Nat.cast_nonneg n)))
#align pochhammer_pos pochhammer_pos

end StrictOrderedSemiring

section Factorial

open Nat

variable (S : Type _) [Semiring S] (r n : ℕ)

#print pochhammer_eval_one /-
@[simp]
theorem pochhammer_eval_one (S : Type _) [Semiring S] (n : ℕ) :
    (pochhammer S n).eval (1 : S) = (n ! : S) := by
  rw_mod_cast [pochhammer_nat_eq_ascFactorial, Nat.zero_ascFactorial]
#align pochhammer_eval_one pochhammer_eval_one
-/

/- warning: factorial_mul_pochhammer -> factorial_mul_pochhammer is a dubious translation:
lean 3 declaration is
  forall (S : Type.{u1}) [_inst_2 : Semiring.{u1} S] (r : Nat) (n : Nat), Eq.{succ u1} S (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (Distrib.toHasMul.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))))) (Nat.factorial r)) (Polynomial.eval.{u1} S _inst_2 (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toHasAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))))) r) (OfNat.ofNat.{u1} S 1 (OfNat.mk.{u1} S 1 (One.one.{u1} S (AddMonoidWithOne.toOne.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2)))))))) (pochhammer.{u1} S _inst_2 n))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))))) (Nat.factorial (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) r n)))
but is expected to have type
  forall (S : Type.{u1}) [_inst_2 : Semiring.{u1} S] (r : Nat) (n : Nat), Eq.{succ u1} S (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (NonUnitalNonAssocSemiring.toMul.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2)))) (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_2) (Nat.factorial r)) (Polynomial.eval.{u1} S _inst_2 (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_2))))) (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_2) r) (OfNat.ofNat.{u1} S 1 (One.toOfNat1.{u1} S (Semiring.toOne.{u1} S _inst_2)))) (pochhammer.{u1} S _inst_2 n))) (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_2) (Nat.factorial (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) r n)))
Case conversion may be inaccurate. Consider using '#align factorial_mul_pochhammer factorial_mul_pochhammerₓ'. -/
theorem factorial_mul_pochhammer (S : Type _) [Semiring S] (r n : ℕ) :
    (r ! : S) * (pochhammer S n).eval (r + 1) = (r + n)! := by
  rw_mod_cast [pochhammer_nat_eq_ascFactorial, Nat.factorial_mul_ascFactorial]
#align factorial_mul_pochhammer factorial_mul_pochhammer

#print pochhammer_nat_eval_succ /-
theorem pochhammer_nat_eval_succ (r : ℕ) :
    ∀ n : ℕ, n * (pochhammer ℕ r).eval (n + 1) = (n + r) * (pochhammer ℕ r).eval n
  | 0 => by
    by_cases h : r = 0
    · simp only [h, MulZeroClass.zero_mul, zero_add]
    · simp only [pochhammer_eval_zero, MulZeroClass.zero_mul, if_neg h, MulZeroClass.mul_zero]
  | k + 1 => by simp only [pochhammer_nat_eq_ascFactorial, Nat.succ_ascFactorial, add_right_comm]
#align pochhammer_nat_eval_succ pochhammer_nat_eval_succ
-/

/- warning: pochhammer_eval_succ -> pochhammer_eval_succ is a dubious translation:
lean 3 declaration is
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S] (r : Nat) (n : Nat), Eq.{succ u1} S (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (Distrib.toHasMul.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) n) (Polynomial.eval.{u1} S _inst_1 (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toHasAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) n) (OfNat.ofNat.{u1} S 1 (OfNat.mk.{u1} S 1 (One.one.{u1} S (AddMonoidWithOne.toOne.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1)))))))) (pochhammer.{u1} S _inst_1 r))) (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (Distrib.toHasMul.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))) (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toHasAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) n) ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) r)) (Polynomial.eval.{u1} S _inst_1 ((fun (a : Type) (b : Type.{u1}) [self : HasLiftT.{1, succ u1} a b] => self.0) Nat S (HasLiftT.mk.{1, succ u1} Nat S (CoeTCₓ.coe.{1, succ u1} Nat S (Nat.castCoe.{u1} S (AddMonoidWithOne.toNatCast.{u1} S (AddCommMonoidWithOne.toAddMonoidWithOne.{u1} S (NonAssocSemiring.toAddCommMonoidWithOne.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))))) n) (pochhammer.{u1} S _inst_1 r)))
but is expected to have type
  forall (S : Type.{u1}) [_inst_1 : Semiring.{u1} S] (r : Nat) (n : Nat), Eq.{succ u1} S (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (NonUnitalNonAssocSemiring.toMul.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1)))) (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_1) n) (Polynomial.eval.{u1} S _inst_1 (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))) (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_1) n) (OfNat.ofNat.{u1} S 1 (One.toOfNat1.{u1} S (Semiring.toOne.{u1} S _inst_1)))) (pochhammer.{u1} S _inst_1 r))) (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (NonUnitalNonAssocSemiring.toMul.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1)))) (HAdd.hAdd.{u1, u1, u1} S S S (instHAdd.{u1} S (Distrib.toAdd.{u1} S (NonUnitalNonAssocSemiring.toDistrib.{u1} S (NonAssocSemiring.toNonUnitalNonAssocSemiring.{u1} S (Semiring.toNonAssocSemiring.{u1} S _inst_1))))) (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_1) n) (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_1) r)) (Polynomial.eval.{u1} S _inst_1 (Nat.cast.{u1} S (Semiring.toNatCast.{u1} S _inst_1) n) (pochhammer.{u1} S _inst_1 r)))
Case conversion may be inaccurate. Consider using '#align pochhammer_eval_succ pochhammer_eval_succₓ'. -/
theorem pochhammer_eval_succ (r n : ℕ) :
    (n : S) * (pochhammer S r).eval (n + 1 : S) = (n + r) * (pochhammer S r).eval n := by
  exact_mod_cast congr_arg Nat.cast (pochhammer_nat_eval_succ r n)
#align pochhammer_eval_succ pochhammer_eval_succ

end Factorial


theorem plus_comm : ∀ n m : nat, n + m = m + n := begin
    intros,
    apply nat.add_comm
end


import game.series.tempLevel01
variable X : Type --hide

namespace xena

/-
Idea 02: if $\forall n \in \mathbb{N}, a_n \ge 0$,
$\sum a_n$ converges iff partial sums bounded above.
-/

/- Lemma
If $A$ and $B$ are sets of any type $X$, then
$$ A \subseteq A\cup B.$$
-/
theorem subset_union_left (A B : set X) : A ⊆ A ∪ B :=
begin
    --change ∀ (x : α), x ∈ A → x ∈ A ∪ B,  --they may want to do this
    intros x hx,
    left, exact hx, done
end

end xena


/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Floris van Doorn
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.limits.shapes.products
import Mathlib.category_theory.discrete_category
import Mathlib.PostPort

universes v u

namespace Mathlib

namespace category_theory.limits

/--
If `F.left_op : Jᵒᵖ ⥤ C` has a colimit, we can construct a limit for `F : J ⥤ Cᵒᵖ`.
-/
theorem has_limit_of_has_colimit_left_op {C : Type u} [category C] {J : Type v} [small_category J] (F : J ⥤ (Cᵒᵖ)) [has_colimit (functor.left_op F)] : has_limit F :=
  has_limit.mk
    (limit_cone.mk (cone_of_cocone_left_op (colimit.cocone (functor.left_op F)))
      (is_limit.mk fun (s : cone F) => has_hom.hom.op (colimit.desc (functor.left_op F) (cocone_left_op_of_cone s))))

/--
If `C` has colimits of shape `Jᵒᵖ`, we can construct limits in `Cᵒᵖ` of shape `J`.
-/
theorem has_limits_of_shape_op_of_has_colimits_of_shape {C : Type u} [category C] {J : Type v} [small_category J] [has_colimits_of_shape (Jᵒᵖ) C] : has_limits_of_shape J (Cᵒᵖ) :=
  has_limits_of_shape.mk fun (F : J ⥤ (Cᵒᵖ)) => has_limit_of_has_colimit_left_op F

/--
If `C` has colimits, we can construct limits for `Cᵒᵖ`.
-/
theorem has_limits_op_of_has_colimits {C : Type u} [category C] [has_colimits C] : has_limits (Cᵒᵖ) :=
  has_limits.mk fun (J : Type v) (𝒥 : small_category J) => has_limits_of_shape_op_of_has_colimits_of_shape

/--
If `F.left_op : Jᵒᵖ ⥤ C` has a limit, we can construct a colimit for `F : J ⥤ Cᵒᵖ`.
-/
theorem has_colimit_of_has_limit_left_op {C : Type u} [category C] {J : Type v} [small_category J] (F : J ⥤ (Cᵒᵖ)) [has_limit (functor.left_op F)] : has_colimit F :=
  has_colimit.mk
    (colimit_cocone.mk (cocone_of_cone_left_op (limit.cone (functor.left_op F)))
      (is_colimit.mk fun (s : cocone F) => has_hom.hom.op (limit.lift (functor.left_op F) (cone_left_op_of_cocone s))))

/--
If `C` has colimits of shape `Jᵒᵖ`, we can construct limits in `Cᵒᵖ` of shape `J`.
-/
theorem has_colimits_of_shape_op_of_has_limits_of_shape {C : Type u} [category C] {J : Type v} [small_category J] [has_limits_of_shape (Jᵒᵖ) C] : has_colimits_of_shape J (Cᵒᵖ) :=
  has_colimits_of_shape.mk fun (F : J ⥤ (Cᵒᵖ)) => has_colimit_of_has_limit_left_op F

/--
If `C` has limits, we can construct colimits for `Cᵒᵖ`.
-/
theorem has_colimits_op_of_has_limits {C : Type u} [category C] [has_limits C] : has_colimits (Cᵒᵖ) :=
  has_colimits.mk fun (J : Type v) (𝒥 : small_category J) => has_colimits_of_shape_op_of_has_limits_of_shape

/--
If `C` has products indexed by `X`, then `Cᵒᵖ` has coproducts indexed by `X`.
-/
theorem has_coproducts_opposite {C : Type u} [category C] (X : Type v) [has_products_of_shape X C] : has_coproducts_of_shape X (Cᵒᵖ) :=
  has_colimits_of_shape_op_of_has_limits_of_shape

/--
If `C` has coproducts indexed by `X`, then `Cᵒᵖ` has products indexed by `X`.
-/
theorem has_products_opposite {C : Type u} [category C] (X : Type v) [has_coproducts_of_shape X C] : has_products_of_shape X (Cᵒᵖ) :=
  has_limits_of_shape_op_of_has_colimits_of_shape


variable (p : Prop)

open Classical

theorem dne (h : ¬¬p) : p :=
  Or.elim (em p)
  (fun (hp : p) => hp)
  (fun (hnp: ¬p) => absurd hnp h)

theorem step (h : ¬(p ∨ ¬ p)) : ¬p :=
  fun (hp : p) => h (Or.intro_left (¬p) (hp))

theorem exclmid : p ∨ ¬p :=
  dne (p ∨ ¬p) (
    fun (h : ¬(p ∨ ¬p)) =>
    h (Or.intro_right (p) (step p h))
  )

#check dne
#check p ∨ ¬p
#check dne (p ∨ ¬p)


/-
Copyright (c) 2020 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import analysis.inner_product_space.basic
import analysis.special_functions.sqrt

/-!
# Derivative of the inner product

In this file we prove that the inner product and square of the norm in an inner space are
infinitely `ℝ`-smooth. In order to state these results, we need a `normed_space ℝ E`
instance. Though we can deduce this structure from `inner_product_space 𝕜 E`, this instance may be
not definitionally equal to some other “natural” instance. So, we assume `[normed_space ℝ E]`.
-/

noncomputable theory

open is_R_or_C real filter
open_locale big_operators classical topological_space

variables {𝕜 E F : Type*} [is_R_or_C 𝕜]
variables [inner_product_space 𝕜 E] [inner_product_space ℝ F]
local notation `⟪`x`, `y`⟫` := @inner 𝕜 _ _ x y

variables [normed_space ℝ E]

/-- Derivative of the inner product. -/
def fderiv_inner_clm (p : E × E) : E × E →L[ℝ] 𝕜 := is_bounded_bilinear_map_inner.deriv p

@[simp] lemma fderiv_inner_clm_apply (p x : E × E) :
  fderiv_inner_clm  p x = ⟪p.1, x.2⟫ + ⟪x.1, p.2⟫ := rfl

lemma times_cont_diff_inner {n} : times_cont_diff ℝ n (λ p : E × E, ⟪p.1, p.2⟫) :=
is_bounded_bilinear_map_inner.times_cont_diff

lemma times_cont_diff_at_inner {p : E × E} {n} :
  times_cont_diff_at ℝ n (λ p : E × E, ⟪p.1, p.2⟫) p :=
times_cont_diff_inner.times_cont_diff_at

lemma differentiable_inner : differentiable ℝ (λ p : E × E, ⟪p.1, p.2⟫) :=
is_bounded_bilinear_map_inner.differentiable_at

variables {G : Type*} [normed_group G] [normed_space ℝ G]
  {f g : G → E} {f' g' : G →L[ℝ] E} {s : set G} {x : G} {n : with_top ℕ}

include 𝕜

lemma times_cont_diff_within_at.inner (hf : times_cont_diff_within_at ℝ n f s x)
  (hg : times_cont_diff_within_at ℝ n g s x) :
  times_cont_diff_within_at ℝ n (λ x, ⟪f x, g x⟫) s x :=
times_cont_diff_at_inner.comp_times_cont_diff_within_at x (hf.prod hg)

lemma times_cont_diff_at.inner (hf : times_cont_diff_at ℝ n f x)
  (hg : times_cont_diff_at ℝ n g x) :
  times_cont_diff_at ℝ n (λ x, ⟪f x, g x⟫) x :=
hf.inner hg

lemma times_cont_diff_on.inner (hf : times_cont_diff_on ℝ n f s) (hg : times_cont_diff_on ℝ n g s) :
  times_cont_diff_on ℝ n (λ x, ⟪f x, g x⟫) s :=
λ x hx, (hf x hx).inner (hg x hx)

lemma times_cont_diff.inner (hf : times_cont_diff ℝ n f) (hg : times_cont_diff ℝ n g) :
  times_cont_diff ℝ n (λ x, ⟪f x, g x⟫) :=
times_cont_diff_inner.comp (hf.prod hg)

lemma has_fderiv_within_at.inner (hf : has_fderiv_within_at f f' s x)
  (hg : has_fderiv_within_at g g' s x) :
  has_fderiv_within_at (λ t, ⟪f t, g t⟫) ((fderiv_inner_clm (f x, g x)).comp $ f'.prod g') s x :=
(is_bounded_bilinear_map_inner.has_fderiv_at (f x, g x)).comp_has_fderiv_within_at x (hf.prod hg)

lemma has_strict_fderiv_at.inner (hf : has_strict_fderiv_at f f' x)
  (hg : has_strict_fderiv_at g g' x) :
  has_strict_fderiv_at (λ t, ⟪f t, g t⟫) ((fderiv_inner_clm (f x, g x)).comp $ f'.prod g') x :=
(is_bounded_bilinear_map_inner.has_strict_fderiv_at (f x, g x)).comp x (hf.prod hg)

lemma has_fderiv_at.inner (hf : has_fderiv_at f f' x) (hg : has_fderiv_at g g' x) :
  has_fderiv_at (λ t, ⟪f t, g t⟫) ((fderiv_inner_clm (f x, g x)).comp $ f'.prod g') x :=
(is_bounded_bilinear_map_inner.has_fderiv_at (f x, g x)).comp x (hf.prod hg)

lemma has_deriv_within_at.inner {f g : ℝ → E} {f' g' : E} {s : set ℝ} {x : ℝ}
  (hf : has_deriv_within_at f f' s x) (hg : has_deriv_within_at g g' s x) :
  has_deriv_within_at (λ t, ⟪f t, g t⟫) (⟪f x, g'⟫ + ⟪f', g x⟫) s x :=
by simpa using (hf.has_fderiv_within_at.inner hg.has_fderiv_within_at).has_deriv_within_at

lemma has_deriv_at.inner {f g : ℝ → E} {f' g' : E} {x : ℝ} :
  has_deriv_at f f' x →  has_deriv_at g g' x →
  has_deriv_at (λ t, ⟪f t, g t⟫) (⟪f x, g'⟫ + ⟪f', g x⟫) x :=
by simpa only [← has_deriv_within_at_univ] using has_deriv_within_at.inner

lemma differentiable_within_at.inner (hf : differentiable_within_at ℝ f s x)
  (hg : differentiable_within_at ℝ g s x) :
  differentiable_within_at ℝ (λ x, ⟪f x, g x⟫) s x :=
((differentiable_inner _).has_fderiv_at.comp_has_fderiv_within_at x
  (hf.prod hg).has_fderiv_within_at).differentiable_within_at

lemma differentiable_at.inner (hf : differentiable_at ℝ f x) (hg : differentiable_at ℝ g x) :
  differentiable_at ℝ (λ x, ⟪f x, g x⟫) x :=
(differentiable_inner _).comp x (hf.prod hg)

lemma differentiable_on.inner (hf : differentiable_on ℝ f s) (hg : differentiable_on ℝ g s) :
  differentiable_on ℝ (λ x, ⟪f x, g x⟫) s :=
λ x hx, (hf x hx).inner (hg x hx)

lemma differentiable.inner (hf : differentiable ℝ f) (hg : differentiable ℝ g) :
  differentiable ℝ (λ x, ⟪f x, g x⟫) :=
λ x, (hf x).inner (hg x)

lemma fderiv_inner_apply (hf : differentiable_at ℝ f x) (hg : differentiable_at ℝ g x) (y : G) :
  fderiv ℝ (λ t, ⟪f t, g t⟫) x y = ⟪f x, fderiv ℝ g x y⟫ + ⟪fderiv ℝ f x y, g x⟫ :=
by { rw [(hf.has_fderiv_at.inner hg.has_fderiv_at).fderiv], refl }

lemma deriv_inner_apply {f g : ℝ → E} {x : ℝ} (hf : differentiable_at ℝ f x)
  (hg : differentiable_at ℝ g x) :
  deriv (λ t, ⟪f t, g t⟫) x = ⟪f x, deriv g x⟫ + ⟪deriv f x, g x⟫ :=
(hf.has_deriv_at.inner hg.has_deriv_at).deriv

lemma times_cont_diff_norm_sq : times_cont_diff ℝ n (λ x : E, ∥x∥ ^ 2) :=
begin
  simp only [sq, ← inner_self_eq_norm_mul_norm],
  exact (re_clm : 𝕜 →L[ℝ] ℝ).times_cont_diff.comp (times_cont_diff_id.inner times_cont_diff_id)
end

lemma times_cont_diff.norm_sq (hf : times_cont_diff ℝ n f) :
  times_cont_diff ℝ n (λ x, ∥f x∥ ^ 2) :=
times_cont_diff_norm_sq.comp hf

lemma times_cont_diff_within_at.norm_sq (hf : times_cont_diff_within_at ℝ n f s x) :
  times_cont_diff_within_at ℝ n (λ y, ∥f y∥ ^ 2) s x :=
times_cont_diff_norm_sq.times_cont_diff_at.comp_times_cont_diff_within_at x hf

lemma times_cont_diff_at.norm_sq (hf : times_cont_diff_at ℝ n f x) :
  times_cont_diff_at ℝ n (λ y, ∥f y∥ ^ 2) x :=
hf.norm_sq

lemma times_cont_diff_at_norm {x : E} (hx : x ≠ 0) : times_cont_diff_at ℝ n norm x :=
have ∥id x∥ ^ 2 ≠ 0, from pow_ne_zero _ (norm_pos_iff.2 hx).ne',
by simpa only [id, sqrt_sq, norm_nonneg] using times_cont_diff_at_id.norm_sq.sqrt this

lemma times_cont_diff_at.norm (hf : times_cont_diff_at ℝ n f x) (h0 : f x ≠ 0) :
  times_cont_diff_at ℝ n (λ y, ∥f y∥) x :=
(times_cont_diff_at_norm h0).comp x hf

lemma times_cont_diff_at.dist (hf : times_cont_diff_at ℝ n f x) (hg : times_cont_diff_at ℝ n g x)
  (hne : f x ≠ g x) :
  times_cont_diff_at ℝ n (λ y, dist (f y) (g y)) x :=
by { simp only [dist_eq_norm], exact (hf.sub hg).norm (sub_ne_zero.2 hne) }

lemma times_cont_diff_within_at.norm (hf : times_cont_diff_within_at ℝ n f s x) (h0 : f x ≠ 0) :
  times_cont_diff_within_at ℝ n (λ y, ∥f y∥) s x :=
(times_cont_diff_at_norm h0).comp_times_cont_diff_within_at x hf

lemma times_cont_diff_within_at.dist (hf : times_cont_diff_within_at ℝ n f s x)
  (hg : times_cont_diff_within_at ℝ n g s x) (hne : f x ≠ g x) :
  times_cont_diff_within_at ℝ n (λ y, dist (f y) (g y)) s x :=
by { simp only [dist_eq_norm], exact (hf.sub hg).norm (sub_ne_zero.2 hne) }

lemma times_cont_diff_on.norm_sq (hf : times_cont_diff_on ℝ n f s) :
  times_cont_diff_on ℝ n (λ y, ∥f y∥ ^ 2) s :=
(λ x hx, (hf x hx).norm_sq)

lemma times_cont_diff_on.norm (hf : times_cont_diff_on ℝ n f s) (h0 : ∀ x ∈ s, f x ≠ 0) :
  times_cont_diff_on ℝ n (λ y, ∥f y∥) s :=
λ x hx, (hf x hx).norm (h0 x hx)

lemma times_cont_diff_on.dist (hf : times_cont_diff_on ℝ n f s)
  (hg : times_cont_diff_on ℝ n g s) (hne : ∀ x ∈ s, f x ≠ g x) :
  times_cont_diff_on ℝ n (λ y, dist (f y) (g y)) s :=
λ x hx, (hf x hx).dist (hg x hx) (hne x hx)

lemma times_cont_diff.norm (hf : times_cont_diff ℝ n f) (h0 : ∀ x, f x ≠ 0) :
  times_cont_diff ℝ n (λ y, ∥f y∥) :=
times_cont_diff_iff_times_cont_diff_at.2 $ λ x, hf.times_cont_diff_at.norm (h0 x)

lemma times_cont_diff.dist (hf : times_cont_diff ℝ n f) (hg : times_cont_diff ℝ n g)
  (hne : ∀ x, f x ≠ g x) :
  times_cont_diff ℝ n (λ y, dist (f y) (g y)) :=
times_cont_diff_iff_times_cont_diff_at.2 $
  λ x, hf.times_cont_diff_at.dist hg.times_cont_diff_at (hne x)

omit 𝕜
lemma has_strict_fderiv_at_norm_sq (x : F) :
  has_strict_fderiv_at (λ x, ∥x∥ ^ 2) (bit0 (innerSL x)) x :=
begin
  simp only [sq, ← inner_self_eq_norm_mul_norm],
  convert (has_strict_fderiv_at_id x).inner (has_strict_fderiv_at_id x),
  ext y,
  simp [bit0, real_inner_comm],
end
include 𝕜

lemma differentiable_at.norm_sq (hf : differentiable_at ℝ f x) :
  differentiable_at ℝ (λ y, ∥f y∥ ^ 2) x :=
(times_cont_diff_at_id.norm_sq.differentiable_at le_rfl).comp x hf

lemma differentiable_at.norm (hf : differentiable_at ℝ f x) (h0 : f x ≠ 0) :
  differentiable_at ℝ (λ y, ∥f y∥) x :=
((times_cont_diff_at_norm h0).differentiable_at le_rfl).comp x hf

lemma differentiable_at.dist (hf : differentiable_at ℝ f x) (hg : differentiable_at ℝ g x)
  (hne : f x ≠ g x) :
  differentiable_at ℝ (λ y, dist (f y) (g y)) x :=
by { simp only [dist_eq_norm], exact (hf.sub hg).norm (sub_ne_zero.2 hne) }

lemma differentiable.norm_sq (hf : differentiable ℝ f) : differentiable ℝ (λ y, ∥f y∥ ^ 2) :=
λ x, (hf x).norm_sq

lemma differentiable.norm (hf : differentiable ℝ f) (h0 : ∀ x, f x ≠ 0) :
  differentiable ℝ (λ y, ∥f y∥) :=
λ x, (hf x).norm (h0 x)

lemma differentiable.dist (hf : differentiable ℝ f) (hg : differentiable ℝ g)
  (hne : ∀ x, f x ≠ g x) :
  differentiable ℝ (λ y, dist (f y) (g y)) :=
λ x, (hf x).dist (hg x) (hne x)

lemma differentiable_within_at.norm_sq (hf : differentiable_within_at ℝ f s x) :
  differentiable_within_at ℝ (λ y, ∥f y∥ ^ 2) s x :=
(times_cont_diff_at_id.norm_sq.differentiable_at le_rfl).comp_differentiable_within_at x hf

lemma differentiable_within_at.norm (hf : differentiable_within_at ℝ f s x) (h0 : f x ≠ 0) :
  differentiable_within_at ℝ (λ y, ∥f y∥) s x :=
((times_cont_diff_at_id.norm h0).differentiable_at le_rfl).comp_differentiable_within_at x hf

lemma differentiable_within_at.dist (hf : differentiable_within_at ℝ f s x)
  (hg : differentiable_within_at ℝ g s x) (hne : f x ≠ g x) :
  differentiable_within_at ℝ (λ y, dist (f y) (g y)) s x :=
by { simp only [dist_eq_norm], exact (hf.sub hg).norm (sub_ne_zero.2 hne) }

lemma differentiable_on.norm_sq (hf : differentiable_on ℝ f s) :
  differentiable_on ℝ (λ y, ∥f y∥ ^ 2) s :=
λ x hx, (hf x hx).norm_sq

lemma differentiable_on.norm (hf : differentiable_on ℝ f s) (h0 : ∀ x ∈ s, f x ≠ 0) :
  differentiable_on ℝ (λ y, ∥f y∥) s :=
λ x hx, (hf x hx).norm (h0 x hx)

lemma differentiable_on.dist (hf : differentiable_on ℝ f s) (hg : differentiable_on ℝ g s)
  (hne : ∀ x ∈ s, f x ≠ g x) :
  differentiable_on ℝ (λ y, dist (f y) (g y)) s :=
λ x hx, (hf x hx).dist (hg x hx) (hne x hx)


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

import data.multiset.basic
import data.vector.basic
import tactic.apply_fun

/-!
# Symmetric powers

This file defines symmetric powers of a type.  The nth symmetric power
consists of homogeneous n-tuples modulo permutations by the symmetric
group.

The special case of 2-tuples is called the symmetric square, which is
addressed in more detail in `data.sym.sym2`.

TODO: This was created as supporting material for `sym2`; it
needs a fleshed-out interface.

## Tags

symmetric powers

-/

universes u

/--
The nth symmetric power is n-tuples up to permutation.  We define it
as a subtype of `multiset` since these are well developed in the
library.  We also give a definition `sym.sym'` in terms of vectors, and we
show these are equivalent in `sym.sym_equiv_sym'`.
-/
def sym (α : Type u) (n : ℕ) := {s : multiset α // s.card = n}

/--
This is the `list.perm` setoid lifted to `vector`.

See note [reducible non-instances].
-/
@[reducible]
def vector.perm.is_setoid (α : Type u) (n : ℕ) : setoid (vector α n) :=
{ r := λ a b, list.perm a.1 b.1,
  iseqv := by { rcases list.perm.eqv α with ⟨hr, hs, ht⟩, tidy, } }

local attribute [instance] vector.perm.is_setoid

namespace sym

variables {α : Type u} {n : ℕ}

/--
This is the quotient map that takes a list of n elements as an n-tuple and produces an nth
symmetric power.
-/
def of_vector (x : vector α n) : sym α n :=
⟨↑x.val, by { rw multiset.coe_card, exact x.2 }⟩

instance : has_lift (vector α n) (sym α n) :=
{ lift := of_vector }

/--
The unique element in `sym α 0`.
-/
@[pattern] def nil : sym α 0 := ⟨0, by tidy⟩

/--
Inserts an element into the term of `sym α n`, increasing the length by one.
-/
@[pattern] def cons : α → sym α n → sym α (nat.succ n)
| a ⟨s, h⟩ := ⟨a ::ₘ s, by rw [multiset.card_cons, h]⟩

notation a :: b := cons a b

@[simp]
lemma cons_inj_right (a : α) (s s' : sym α n) : a :: s = a :: s' ↔ s = s' :=
by { cases s, cases s', delta cons, simp, }

@[simp]
lemma cons_inj_left (a a' : α) (s : sym α n) : a :: s = a' :: s ↔ a = a' :=
by { cases s, delta cons, simp, }

lemma cons_swap (a b : α) (s : sym α n) : a :: b :: s = b :: a :: s :=
by { cases s, ext, delta cons, rw subtype.coe_mk, dsimp, exact multiset.cons_swap a b s_val }

/--
`α ∈ s` means that `a` appears as one of the factors in `s`.
-/
def mem (a : α) (s : sym α n) : Prop := a ∈ s.1

instance : has_mem α (sym α n) := ⟨mem⟩

instance decidable_mem [decidable_eq α] (a : α) (s : sym α n) : decidable (a ∈ s) :=
by { cases s, change decidable (a ∈ s_val), apply_instance }

@[simp] lemma mem_cons {a b : α} {s : sym α n} : a ∈ b :: s ↔ a = b ∨ a ∈ s :=
begin cases s, change a ∈ b ::ₘ s_val ↔ a = b ∨ a ∈ s_val, simp, end

lemma mem_cons_of_mem {a b : α} {s : sym α n} (h : a ∈ s) : a ∈ b :: s :=
mem_cons.2 (or.inr h)

@[simp] lemma mem_cons_self (a : α) (s : sym α n) : a ∈ a :: s :=
mem_cons.2 (or.inl rfl)

lemma sound {a b : vector α n} (h : a.val ~ b.val) : (↑a : sym α n) = ↑b :=
begin
  cases a, cases b, unfold_coes, dunfold of_vector,
  simp only [subtype.mk_eq_mk, multiset.coe_eq_coe],
  exact h,
end

/--
Another definition of the nth symmetric power, using vectors modulo permutations. (See `sym`.)
-/
def sym' (α : Type u) (n : ℕ) := quotient (vector.perm.is_setoid α n)

/--
This is `cons` but for the alternative `sym'` definition.
-/
def cons' {α : Type u} {n : ℕ} : α → sym' α n → sym' α (nat.succ n) :=
λ a, quotient.map (vector.cons a) (λ ⟨l₁, h₁⟩ ⟨l₂, h₂⟩ h, list.perm.cons _ h)

notation a :: b := cons' a b

/--
Multisets of cardinality n are equivalent to length-n vectors up to permutations.
-/
def sym_equiv_sym' {α : Type u} {n : ℕ} : sym α n ≃ sym' α n :=
equiv.subtype_quotient_equiv_quotient_subtype _ _ (λ _, by refl) (λ _ _, by refl)

lemma cons_equiv_eq_equiv_cons (α : Type u) (n : ℕ) (a : α) (s : sym α n) :
  a :: sym_equiv_sym' s = sym_equiv_sym' (a :: s) :=
by tidy

section inhabited
-- Instances to make the linter happy

instance inhabited_sym [inhabited α] (n : ℕ) : inhabited (sym α n) :=
⟨⟨multiset.repeat (default α) n, multiset.card_repeat _ _⟩⟩

instance inhabited_sym' [inhabited α] (n : ℕ) : inhabited (sym' α n) :=
⟨quotient.mk' (vector.repeat (default α) n)⟩

end inhabited

end sym


/-
Copyright (c) 2021 Paula Neeley. All rights reserved.
Author: Paula Neeley
Following the textbook "Dynamic Epistemic Logic" by
Hans van Ditmarsch, Wiebe van der Hoek, and Barteld Kooi
-/

import del.languageDEL del.syntax.syntaxDEL data.set.basic
local attribute [instance] classical.prop_decidable

variables {agents : Type}
open prfPA

---------------------- Helper Lemmas ----------------------

namespace PAlemma

lemma iden {Γ : ctxPA agents} {φ : formPA agents} :
  prfPA Γ (φ ⊃ φ) :=
begin
exact mp (mp (@pl2 _ _ φ (φ ⊃ φ) φ) pl1) pl1
end

lemma prtrue {Γ : ctxPA agents} : prfPA Γ ¬⊥ :=
begin
exact iden
end

lemma weak {Γ : ctxPA agents} {φ ψ : formPA agents} :
  prfPA Γ φ → prfPA (Γ ∪ ψ) φ :=
begin
intro h,
induction h,
{apply ax, exact (set.mem_insert_of_mem _ h_h)},
{exact pl1},
{exact pl2},
{exact pl3},
{exact pl4},
{exact pl5},
{exact pl6},
{exact pl7},
{exact kdist},
{exact truth},
{exact posintro},
{exact negintro},
{apply mp,
  {exact h_ih_hpq},
  {exact h_ih_hp}},
{exact nec h_ih},
{exact atomicbot},
{exact atomicperm},
{exact announceneg},
{exact announceconj},
{exact announceimp},
{exact announceknow},
{exact announcecomp}
end

lemma pr {Γ : ctxPA agents} {φ : formPA agents} :
  prfPA (Γ ∪ φ) φ :=
begin
apply ax;
apply or.intro_left;
simp
end

lemma cut {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ (φ ⊃ ψ) → prfPA Γ (ψ ⊃ χ) → prfPA Γ (φ ⊃ χ) :=
begin
intros h1 h2,
exact mp (mp pl2 (mp pl1 h2)) h1
end

lemma conv_deduction {Γ : ctxPA agents} {φ ψ : formPA agents} :
  prfPA Γ (φ ⊃ ψ) → prfPA (Γ ∪ φ) ψ :=
begin
intro h,
exact mp (weak h) pr
end

lemma hs1 {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ ((ψ ⊃ χ) ⊃ ((φ ⊃ ψ) ⊃ (φ ⊃ χ))) :=
begin
exact (mp (mp pl2 (mp pl1 pl2)) pl1)
end

lemma likemp {Γ : ctxPA agents} {φ ψ : formPA agents} :
  prfPA Γ (φ ⊃ ((φ ⊃ ψ) ⊃ ψ)) :=
begin
exact (mp (mp hs1 (mp pl2 iden)) pl1)
end

lemma dne {Γ : ctxPA agents} {φ : formPA agents} :
prfPA Γ ((¬¬φ) ⊃ φ) :=
begin
have h1 : prfPA Γ (φ ⊃ (φ ⊃ φ)), from pl1,
exact (cut (cut pl1 (cut pl7 pl7)) (mp likemp h1))
end

lemma dni {Γ : ctxPA agents} {φ : formPA agents} : prfPA Γ (φ ⊃ ¬¬φ) :=
begin
exact mp pl7 dne
end

lemma imp_if_imp_imp {Γ : ctxPA agents} {φ ψ χ : formPA agents} : prfPA Γ (φ ⊃ χ) → prfPA Γ (φ ⊃ (ψ ⊃ χ)) :=
begin
intro h1,
exact mp (mp pl2 (mp pl1 pl1)) h1
end

lemma cut1 {Γ : ctxPA agents} {φ ψ χ θ : formPA agents} :
  prfPA Γ (θ ⊃ (φ ⊃ ψ)) → prfPA Γ (ψ ⊃ χ) → prfPA Γ (θ ⊃ (φ ⊃ χ)) :=
begin
intros h1 h2,
exact (cut h1) (mp pl2 (mp pl1 h2))
end

lemma imp_switch {Γ : ctxPA agents} {φ ψ χ : formPA agents} : prfPA Γ (φ ⊃ (ψ ⊃ χ)) → prfPA Γ (ψ ⊃ (φ ⊃ χ)) :=
begin
intro h1,
exact mp (mp pl2 (mp pl1 (mp pl2 h1))) pl1
end

lemma l2 {Γ : ctxPA agents} {φ ψ χ : formPA agents} : prfPA Γ ((φ ⊃ (ψ ⊃ χ)) ⊃ (ψ ⊃ (φ ⊃ χ))) :=
begin
exact (mp (mp pl2 (cut pl2 hs1)) (mp pl1 pl1))
end

lemma hs2 {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ ((φ ⊃ ψ) ⊃ ((ψ ⊃ χ) ⊃ (φ ⊃ χ))) :=
begin
exact (mp l2 hs1)
end

lemma cut2 {Γ : ctxPA agents} {φ ψ χ θ : formPA agents} :
  prfPA Γ (φ ⊃ ψ) → prfPA Γ (θ ⊃ (ψ ⊃ χ)) → prfPA Γ (θ ⊃ (φ ⊃ χ)) :=
begin
intros h1 h2,
exact imp_switch (cut h1 (imp_switch h2))
end

lemma double_imp {Γ : ctxPA agents} {φ ψ : formPA agents} :
  prfPA Γ ((φ ⊃ (φ ⊃ ψ)) ⊃ (φ ⊃ ψ)) :=
begin
exact mp pl2 (imp_switch iden)
end

lemma imp_imp_iff_imp {Γ : ctxPA agents} {θ φ ψ : formPA agents} :
  prfPA Γ (θ ⊃ (φ ⊃ (φ ⊃ ψ))) ↔ prfPA Γ (θ ⊃ (φ ⊃ ψ)) :=
begin
split,
{intro h1,
exact cut h1 double_imp},
{intro h1,
exact cut h1 pl1}
end

lemma imp_shift {Γ : ctxPA agents} {θ φ ψ χ : formPA agents} :
  prfPA Γ (θ ⊃ (φ ⊃ (ψ ⊃ χ))) ↔ prfPA Γ (θ ⊃ (ψ ⊃ (φ ⊃ χ))) :=
begin
split,
repeat {intro h1, exact cut h1 (cut2 pl1 pl2)}
end

lemma left_and_imp {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ (ψ ⊃ ((φ & ψ) ⊃ χ)) → prfPA Γ ((φ & ψ) ⊃ χ) :=
begin
intro h1,
exact mp double_imp (cut pl6 h1)
end

lemma and_right_imp {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ ((φ & ψ) ⊃ χ) ↔ prfPA Γ (ψ ⊃ (φ ⊃ χ)) :=
begin
split,
{intro h1,
exact mp (cut2 pl1 pl2) (cut1 pl4 h1)},
intro h1,
exact left_and_imp (cut2 pl5 h1)
end

lemma not_and_subst {φ ψ χ : formPA agents} {Γ : ctxPA agents} : prfPA Γ (φ ↔ ψ) → (prfPA Γ ¬(χ & φ) ↔ prfPA Γ ¬(χ & ψ)) :=
begin
intro h1, split,
{intro h2,
exact mp (mp pl3 (mp pl1 h2)) (cut dne (mp double_imp (cut2 (cut pl6 (mp pl6 h1)) (cut pl5 pl4))))},
{intro h2,
exact mp (mp pl3 (mp pl1 h2)) (cut dne (mp double_imp (cut2 (cut pl6 (mp pl5 h1)) (cut pl5 pl4))))},
end

lemma not_contra {Γ : ctxPA agents} {φ : formPA agents} :
  prfPA Γ ¬(φ & ¬φ) :=
begin
exact mp (mp pl3 (cut dne pl6)) (cut dne pl5)
end

lemma phi_and_true {Γ : ctxPA agents} {φ : formPA agents} : prfPA Γ ((φ&(¬⊥)) ↔ φ) :=
begin
exact (mp (mp pl4 pl5) (mp (imp_switch pl4) prtrue))
end

lemma imp_and_and_imp {Γ : ctxPA agents} {φ ψ χ θ : formPA agents} :
  prfPA Γ (((φ ⊃ ψ) & (χ ⊃ θ))) → prfPA Γ (((φ & χ) ⊃ (ψ & θ))) :=
begin
intro h,
exact (mp double_imp (cut (cut pl5 (mp pl5 h)) (cut2 (cut pl6 (mp pl6 h)) pl4)))
end

lemma not_contra_equiv_true {Γ : ctxPA agents} {φ : formPA agents} :
  prfPA Γ (¬(φ & ¬φ) ↔ ¬⊥) :=
begin
exact (mp (mp pl4 (mp pl1 prtrue)) (mp pl1 not_contra))
end

lemma contrapos {Γ : ctxPA agents} {φ ψ : formPA agents} :
  prfPA Γ ((¬ψ) ⊃ (¬φ)) ↔ prfPA Γ (φ ⊃ ψ) :=
begin
split,
intro h1,
exact mp pl7 h1,
intro h1,
exact mp (cut (cut (mp hs1 dni) (mp hs2 dne)) pl7) h1,
end

lemma iff_not {Γ : ctxPA agents} {φ ψ : formPA agents} :
  prfPA Γ (φ ↔ ψ) → prfPA Γ (¬ψ ↔ ¬φ) :=
begin
intro h1,
have h2 : prfPA Γ (φ ⊃ ψ), from mp pl5 h1,
have h3 : prfPA Γ (ψ ⊃ φ), from mp pl6 h1,
rw ←contrapos at h2,
rw ←contrapos at h3,
exact (mp (mp pl4 h2) h3)
end

lemma contra_equiv_false {Γ : ctxPA agents} {φ : formPA agents} :
  prfPA Γ ((φ & ¬φ) ↔ ⊥) :=
begin
have h1 := iff_not not_contra_equiv_true,
exact (mp (mp pl4 (cut dni (cut (mp pl6 h1) dne))) (cut dni (cut (mp pl5 h1) dne)))
end

lemma and_switch {Γ : ctxPA agents} {φ ψ : formPA agents} : prfPA Γ ((φ & ψ) ↔ (ψ & φ)) :=
begin
exact (mp (mp pl4 (mp double_imp (cut pl5 (imp_switch (cut pl6 pl4)))))
(mp double_imp (cut pl5 (imp_switch (cut pl6 pl4)))))
end

lemma imp_and_imp {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ (φ ⊃ ψ) → prfPA Γ  ((χ & φ) ⊃ (χ & ψ)) :=
begin
intros h1,
exact imp_and_and_imp (mp (mp pl4 iden) h1)
end

lemma iff_iff_and_iff {Γ : ctxPA agents} {φ ψ χ θ : formPA agents} :
  prfPA Γ (φ ↔ χ) → prfPA Γ (ψ ↔ θ) → prfPA Γ ((φ & ψ) ↔ (χ & θ)) :=
begin
intros h1 h2,
exact mp (mp pl4 (imp_and_and_imp (mp (mp pl4 (mp pl5 h1)) (mp pl5 h2))))
  (imp_and_and_imp (mp (mp pl4 (mp pl6 h1)) (mp pl6 h2)))
end

lemma and_commute {Γ : ctxPA agents} {φ ψ χ : formPA agents} : prfPA Γ (((φ & ψ) & χ) ↔ (φ & (ψ & χ))) :=
begin
exact mp (mp pl4 (mp double_imp (imp_imp_iff_imp.mp
  (cut (cut pl5 pl6) (cut2 pl6 (cut1 pl4 (imp_switch (cut (cut pl5 pl5) pl4))))))))
  (mp double_imp (imp_imp_iff_imp.mp (cut (cut pl6 pl5)
  (imp_switch (cut pl5 (cut1 pl4 (cut2 (cut pl6 pl6) pl4)))))))
end

lemma demorgans {Γ : ctxPA agents} {φ ψ : formPA agents} :
  prfPA Γ (¬(φ & ψ)) ↔ prfPA Γ (φ ⊃ ¬ψ) :=
begin
split,
intro h1,
exact (and_right_imp.mp (mp (contrapos.mpr (mp pl5 and_switch)) h1)),
intro h1,
exact (mp (contrapos.mpr (mp pl5 and_switch)) (and_right_imp.mpr h1))
end

lemma explosion {Γ : ctxPA agents} {ψ : formPA agents} : prfPA Γ (⊥ ⊃ ψ) :=
begin
apply contrapos.mp, exact (mp pl1 iden)
end

lemma exfalso {Γ : ctxPA agents} {φ ψ : formPA agents} : prfPA Γ ((φ & ¬φ) ⊃ ψ) :=
begin
exact cut not_contra explosion
end

lemma box_dn {Γ : ctxPA agents} {φ : formPA agents} {a : agents}  : prfPA Γ ((¬K a φ) ↔ ¬(K a (¬¬φ))) :=
begin
exact mp (mp pl4 (contrapos.mpr (mp kdist (nec dne)))) (contrapos.mpr (mp kdist (nec dni)))
end

lemma dual_equiv1 {Γ : ctxPA agents} {φ : formPA agents} {a : agents} : prfPA Γ ((K a φ) ↔ (¬(¬K a ¬(¬φ)))) :=
begin
exact mp (mp pl4 (cut (contrapos.mp (mp pl6 box_dn)) dni))
  (cut dne (contrapos.mp (mp pl5 box_dn)))
end

end PAlemma


/-
Copyright (c) 2018 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot, Johannes Hölzl
-/
import algebra.ring.prod
import ring_theory.ideal.quotient
import ring_theory.subring.basic
import topology.algebra.group

/-!

# Topological (semi)rings

A topological (semi)ring is a (semi)ring equipped with a topology such that all operations are
continuous. Besides this definition, this file proves that the topological closure of a subring
(resp. an ideal) is a subring (resp. an ideal) and defines products and quotients
of topological (semi)rings.

## Main Results

- `subring.topological_closure`/`subsemiring.topological_closure`: the topological closure of a
  `subring`/`subsemiring` is itself a `sub(semi)ring`.
- `prod.topological_ring`/`prod.topological_ring`: The product of two topological (semi)rings.
- `pi.topological_ring`/`pi.topological_ring`: The arbitrary product of topological (semi)rings.
- `ideal.closure`: The closure of an ideal is an ideal.
- `topological_ring_quotient`: The quotient of a topological ring by an ideal is a topological ring.

-/

open classical set filter topological_space function
open_locale classical topological_space filter

section topological_ring
variables (α : Type*)

/-- A topological (semi)ring is a (semi)ring `R` where addition and multiplication are continuous.
If `R` is a ring, then negation is automatically continuous, as it is multiplication with `-1`. -/
class topological_ring [topological_space α] [semiring α]
  extends has_continuous_add α, has_continuous_mul α : Prop

@[priority 50]
instance discrete_topology.topological_ring {α} [topological_space α] [semiring α]
  [discrete_topology α] : topological_ring α := ⟨⟩

section
variables {α} [topological_space α] [semiring α] [topological_ring α]

namespace subsemiring

instance (S : subsemiring α) :
  topological_ring S :=
{ ..S.to_submonoid.has_continuous_mul,
  ..S.to_add_submonoid.has_continuous_add }

end subsemiring

/-- The (topological-space) closure of a subsemiring of a topological semiring is
itself a subsemiring. -/
def subsemiring.topological_closure (s : subsemiring α) : subsemiring α :=
{ carrier := closure (s : set α),
  ..(s.to_submonoid.topological_closure),
  ..(s.to_add_submonoid.topological_closure ) }

@[simp] lemma subsemiring.topological_closure_coe (s : subsemiring α) :
  (s.topological_closure : set α) = closure (s : set α) :=
rfl

instance subsemiring.topological_closure_topological_ring (s : subsemiring α) :
  topological_ring (s.topological_closure) :=
{ ..s.to_add_submonoid.topological_closure_has_continuous_add,
  ..s.to_submonoid.topological_closure_has_continuous_mul }

lemma subsemiring.subring_topological_closure (s : subsemiring α) :
  s ≤ s.topological_closure :=
subset_closure

lemma subsemiring.is_closed_topological_closure (s : subsemiring α) :
  is_closed (s.topological_closure : set α) :=
by convert is_closed_closure

lemma subsemiring.topological_closure_minimal
  (s : subsemiring α) {t : subsemiring α} (h : s ≤ t) (ht : is_closed (t : set α)) :
  s.topological_closure ≤ t :=
closure_minimal h ht

/-- The product topology on the cartesian product of two topological semirings
  makes the product into a topological semiring. -/
instance {β : Type*} [semiring β] [topological_space β] [topological_ring β] :
  topological_ring (α × β) := {}

instance {β : Type*} {C : β → Type*} [∀ b, topological_space (C b)]
  [Π b, semiring (C b)] [Π b, topological_ring (C b)] : topological_ring (Π b, C b) := {}
end

section
variables {R : Type*} [ring R] [topological_space R]

lemma topological_ring.of_add_group_of_nhds_zero [topological_add_group R]
  (hmul : tendsto (uncurry ((*) : R → R → R)) ((𝓝 0) ×ᶠ (𝓝 0)) $ 𝓝 0)
  (hmul_left : ∀ (x₀ : R), tendsto (λ x : R, x₀ * x) (𝓝 0) $ 𝓝 0)
  (hmul_right : ∀ (x₀ : R), tendsto (λ x : R, x * x₀) (𝓝 0) $ 𝓝 0) : topological_ring R :=
begin
  refine {..‹topological_add_group R›, ..},
  have hleft : ∀ x₀ : R, 𝓝 x₀ = map (λ x, x₀ + x) (𝓝 0), by simp,
  have hadd : tendsto (uncurry ((+) : R → R → R)) ((𝓝 0) ×ᶠ (𝓝 0)) (𝓝 0),
  { rw ← nhds_prod_eq,
    convert continuous_add.tendsto ((0 : R), (0 : R)),
    rw zero_add },
  rw continuous_iff_continuous_at,
  rintro ⟨x₀, y₀⟩,
  rw [continuous_at, nhds_prod_eq, hleft x₀, hleft y₀, hleft (x₀*y₀), filter.prod_map_map_eq,
      tendsto_map'_iff],
  suffices :
    tendsto ((λ (x : R), x + x₀ * y₀) ∘ (λ (p : R × R), p.1 + p.2) ∘
              (λ (p : R × R), (p.1*y₀ + x₀*p.2, p.1*p.2)))
            ((𝓝 0) ×ᶠ (𝓝 0)) (map (λ (x : R), x + x₀ * y₀) $ 𝓝 0),
  { convert this using 1,
    { ext, simp only [comp_app, mul_add, add_mul], abel },
    { simp only [add_comm] } },
  refine tendsto_map.comp (hadd.comp (tendsto.prod_mk _ hmul)),
  exact hadd.comp (((hmul_right y₀).comp tendsto_fst).prod_mk ((hmul_left  x₀).comp tendsto_snd))
end

lemma topological_ring.of_nhds_zero
  (hadd : tendsto (uncurry ((+) : R → R → R)) ((𝓝 0) ×ᶠ (𝓝 0)) $ 𝓝 0)
  (hneg : tendsto (λ x, -x : R → R) (𝓝 0) (𝓝 0))
  (hmul : tendsto (uncurry ((*) : R → R → R)) ((𝓝 0) ×ᶠ (𝓝 0)) $ 𝓝 0)
  (hmul_left : ∀ (x₀ : R), tendsto (λ x : R, x₀ * x) (𝓝 0) $ 𝓝 0)
  (hmul_right : ∀ (x₀ : R), tendsto (λ x : R, x * x₀) (𝓝 0) $ 𝓝 0)
  (hleft : ∀ x₀ : R, 𝓝 x₀ = map (λ x, x₀ + x) (𝓝 0)) : topological_ring R :=
begin
  haveI := topological_add_group.of_comm_of_nhds_zero hadd hneg hleft,
  exact topological_ring.of_add_group_of_nhds_zero hmul hmul_left hmul_right
end

end

variables {α} [ring α] [topological_space α] [topological_ring α]

@[priority 100] -- See note [lower instance priority]
instance topological_ring.to_topological_add_group : topological_add_group α :=
{ continuous_add := continuous_add,
  continuous_neg := by simpa only [neg_one_mul, id.def] using
    (@continuous_const α α _ _ (-1)).mul continuous_id }

/-- In a topological ring, the left-multiplication `add_monoid_hom` is continuous. -/
lemma mul_left_continuous (x : α) : continuous (add_monoid_hom.mul_left x) :=
continuous_const.mul continuous_id

/-- In a topological ring, the right-multiplication `add_monoid_hom` is continuous. -/
lemma mul_right_continuous (x : α) : continuous (add_monoid_hom.mul_right x) :=
continuous_id.mul continuous_const

namespace subring

instance (S : subring α) :
  topological_ring S :=
S.to_subsemiring.topological_ring

end subring

/-- The (topological-space) closure of a subring of a topological semiring is
itself a subring. -/
def subring.topological_closure (S : subring α) : subring α :=
{ carrier := closure (S : set α),
  ..S.to_submonoid.topological_closure,
  ..S.to_add_subgroup.topological_closure }

instance subring.topological_closure_topological_ring (s : subring α) :
  topological_ring (s.topological_closure) :=
{ ..s.to_add_subgroup.topological_closure_topological_add_group,
  ..s.to_submonoid.topological_closure_has_continuous_mul }

lemma subring.subring_topological_closure (s : subring α) :
  s ≤ s.topological_closure := subset_closure

lemma subring.is_closed_topological_closure (s : subring α) :
  is_closed (s.topological_closure : set α) := by convert is_closed_closure

lemma subring.topological_closure_minimal
  (s : subring α) {t : subring α} (h : s ≤ t) (ht : is_closed (t : set α)) :
  s.topological_closure ≤ t := closure_minimal h ht

end topological_ring

section topological_comm_ring
variables {α : Type*} [topological_space α] [comm_ring α] [topological_ring α]

/-- The closure of an ideal in a topological ring as an ideal. -/
def ideal.closure (S : ideal α) : ideal α :=
{ carrier   := closure S,
  smul_mem' := λ c x hx, map_mem_closure (mul_left_continuous _) hx $ λ a, S.mul_mem_left c,
  ..(add_submonoid.topological_closure S.to_add_submonoid) }

@[simp] lemma ideal.coe_closure (S : ideal α) : (S.closure : set α) = closure S := rfl

end topological_comm_ring

section topological_ring
variables {α : Type*} [topological_space α] [comm_ring α] (N : ideal α)
open ideal.quotient

instance topological_ring_quotient_topology : topological_space (α ⧸ N) :=
show topological_space (quotient _), by apply_instance

-- note for the reader: in the following, `mk` is `ideal.quotient.mk`, the canonical map `R → R/I`.

variable [topological_ring α]

lemma quotient_ring.is_open_map_coe : is_open_map (mk N) :=
begin
  intros s s_op,
  change is_open (mk N ⁻¹' (mk N '' s)),
  rw quotient_ring_saturate,
  exact is_open_Union (λ ⟨n, _⟩, is_open_map_add_left n s s_op)
end

lemma quotient_ring.quotient_map_coe_coe : quotient_map (λ p : α × α, (mk N p.1, mk N p.2)) :=
is_open_map.to_quotient_map
((quotient_ring.is_open_map_coe N).prod (quotient_ring.is_open_map_coe N))
((continuous_quot_mk.comp continuous_fst).prod_mk (continuous_quot_mk.comp continuous_snd))
(by rintro ⟨⟨x⟩, ⟨y⟩⟩; exact ⟨(x, y), rfl⟩)

instance topological_ring_quotient : topological_ring (α ⧸ N) :=
{ continuous_add :=
    have cont : continuous (mk N ∘ (λ (p : α × α), p.fst + p.snd)) :=
      continuous_quot_mk.comp continuous_add,
    (quotient_map.continuous_iff (quotient_ring.quotient_map_coe_coe N)).mpr cont,
  continuous_mul :=
    have cont : continuous (mk N ∘ (λ (p : α × α), p.fst * p.snd)) :=
      continuous_quot_mk.comp continuous_mul,
    (quotient_map.continuous_iff (quotient_ring.quotient_map_coe_coe N)).mpr cont }

end topological_ring

/-!
### Lattice of ring topologies
We define a type class `ring_topology α` which endows a ring `α` with a topology such that all ring
operations are continuous.

Ring topologies on a fixed ring `α` are ordered, by reverse inclusion. They form a complete lattice,
with `⊥` the discrete topology and `⊤` the indiscrete topology.

Any function `f : α → β` induces `coinduced f : topological_space α → ring_topology β`. -/

universes u v

/-- A ring topology on a ring `α` is a topology for which addition, negation and multiplication
are continuous. -/
@[ext]
structure ring_topology (α : Type u) [ring α]
  extends topological_space α, topological_ring α : Type u

namespace ring_topology
variables {α : Type*} [ring α]

instance inhabited {α : Type u} [ring α] : inhabited (ring_topology α) :=
⟨{to_topological_space := ⊤,
  continuous_add       := continuous_top,
  continuous_mul       := continuous_top}⟩

@[ext]
lemma ext' {f g : ring_topology α} (h : f.is_open = g.is_open) : f = g :=
by { ext, rw h }

/-- The ordering on ring topologies on the ring `α`.
  `t ≤ s` if every set open in `s` is also open in `t` (`t` is finer than `s`). -/
instance : partial_order (ring_topology α) :=
partial_order.lift ring_topology.to_topological_space $ ext

local notation `cont` := @continuous _ _

private def def_Inf (S : set (ring_topology α)) : ring_topology α :=
let Inf_S' := Inf (to_topological_space '' S) in
{ to_topological_space := Inf_S',
  continuous_add       :=
  begin
    apply continuous_Inf_rng,
    rintros _ ⟨⟨t, tr⟩, haS, rfl⟩, resetI,
    have h := continuous_Inf_dom (set.mem_image_of_mem to_topological_space haS) continuous_id,
    have h_continuous_id := @continuous.prod_map _ _ _ _ t t Inf_S' Inf_S' _ _ h h,
    have h_continuous_add : cont (id _) t (λ (p : α × α), p.fst + p.snd) := continuous_add,
    exact @continuous.comp _ _ _ (id _) (id _) t _ _ h_continuous_add h_continuous_id,
  end,
  continuous_mul       :=
  begin
    apply continuous_Inf_rng,
    rintros _ ⟨⟨t, tr⟩, haS, rfl⟩, resetI,
    have h := continuous_Inf_dom (set.mem_image_of_mem to_topological_space haS) continuous_id,
    have h_continuous_id := @continuous.prod_map _ _ _ _ t t Inf_S' Inf_S' _ _ h h,
    have h_continuous_mul : cont (id _) t (λ (p : α × α), p.fst * p.snd) := continuous_mul,
    exact @continuous.comp _ _ _ (id _) (id _) t _ _ h_continuous_mul h_continuous_id,
  end }

/-- Ring topologies on `α` form a complete lattice, with `⊥` the discrete topology and `⊤` the
indiscrete topology.

The infimum of a collection of ring topologies is the topology generated by all their open sets
(which is a ring topology).

The supremum of two ring topologies `s` and `t` is the infimum of the family of all ring topologies
contained in the intersection of `s` and `t`. -/
instance : complete_semilattice_Inf (ring_topology α) :=
{ Inf    := def_Inf,
  Inf_le := λ S a haS, by { apply topological_space.complete_lattice.Inf_le, use [a, ⟨ haS, rfl⟩] },
  le_Inf :=
  begin
    intros S a hab,
    apply topological_space.complete_lattice.le_Inf,
    rintros _ ⟨b, hbS, rfl⟩,
    exact hab b hbS,
  end,
  ..ring_topology.partial_order }

instance : complete_lattice (ring_topology α) :=
complete_lattice_of_complete_semilattice_Inf _

/--  Given `f : α → β` and a topology on `α`, the coinduced ring topology on `β` is the finest
topology such that `f` is continuous and `β` is a topological ring. -/
def coinduced {α β : Type*} [t : topological_space α] [ring β] (f : α → β) :
  ring_topology β :=
Inf {b : ring_topology β | (topological_space.coinduced f t) ≤ b.to_topological_space}

lemma coinduced_continuous {α β : Type*} [t : topological_space α] [ring β] (f : α → β) :
  cont t (coinduced f).to_topological_space f :=
begin
  rw continuous_iff_coinduced_le,
  refine le_Inf _,
  rintros _ ⟨t', ht', rfl⟩,
  exact ht',
end

/-- The forgetful functor from ring topologies on `a` to additive group topologies on `a`. -/
def to_add_group_topology (t : ring_topology α) : add_group_topology α :=
{ to_topological_space     := t.to_topological_space,
  to_topological_add_group := @topological_ring.to_topological_add_group _ _ t.to_topological_space
    t.to_topological_ring }

/-- The order embedding from ring topologies on `a` to additive group topologies on `a`. -/
def to_add_group_topology.order_embedding : order_embedding (ring_topology α)
  (add_group_topology α) :=
{ to_fun       := λ t, t.to_add_group_topology,
  inj'         :=
  begin
    intros t₁ t₂ h_eq,
    dsimp only at h_eq,
    ext,
    have h_t₁ : t₁.to_topological_space = t₁.to_add_group_topology.to_topological_space := rfl,
    rw [h_t₁, h_eq],
    refl,
  end,
  map_rel_iff' :=
  begin
    intros t₁ t₂,
    rw [embedding.coe_fn_mk],
    have h_le : t₁ ≤ t₂ ↔ t₁.to_topological_space ≤ t₂.to_topological_space := by refl,
    rw h_le,
    refl,
  end }

end ring_topology


import quantum_lemmas

open Matrix

------------------------------------------------------------------------------
-- Single qubit random number generator circuit

meta def solve_rng :=
    `[  intros,
        unfold quantum.measure,
        unfold_qubits,
        simp,
        try { unfold H },
        grind_dot_product; `[try {solve1 {simp <|> dec_trivial}}],
        repeat {destruct_fin; `[try {simp}; try {solve1 {ring}}]}]

-- Simply apply `H` gate to `|0⟩`. The result state has the same probability
-- measured in any standard basis.
example : ∀ i, ⟦H ⬝ |0⟩⟧ i = 1/2 := by solve_rng

-- Same result when `|1⟩` is used as the input state.
example : ∀ i, ⟦H ⬝ |1⟩⟧ i = 1/2 := by solve_rng

------------------------------------------------------------------------------
-- Proof automation lemmas
-- These ground facts were needed to simplify `H2 ⬝ |00⟩` evaluations.

@[simp] lemma fin_one_succ_succ_div_2 : (⟨(1 : ℕ).succ.succ / 2, by dec_trivial⟩ : fin 2) = ⟨1, by dec_trivial⟩ := by norm_num
@[simp] lemma fin_one_succ_succ_mod_2 : (⟨(1 : ℕ).succ.succ % 2, by dec_trivial⟩ : fin 2) = ⟨1, by dec_trivial⟩ := by norm_num

------------------------------------------------------------------------------
-- 2 qubit random number generator circuit

-- 2-qubit H circut
noncomputable
def H2 : Square 4 := H ⊗ H

meta def solve_rng2 :=
    `[  intros,
        unfold quantum.measure,
        unfold_qubits,
        simp,
        try { unfold H2 },
        try { unfold H },
        try { unfold kron kron_div kron_mod },
        grind_dot_product; `[try {solve1 {simp <|> dec_trivial}}],
        repeat {destruct_fin; `[try {simp}; try {solve1 {ring}}]}]

-- H2 is a 2-qubit random number generator. The input qubits can be either 0/1 individually.
example : ∀ (i : fin 4), ⟦H2 ⬝ |00⟩⟧ i = 1/4 := by solve_rng2
example : ∀ (i : fin 4), ⟦H2 ⬝ |01⟩⟧ i = 1/4 := by solve_rng2
example : ∀ (i : fin 4), ⟦H2 ⬝ |10⟩⟧ i = 1/4 := by solve_rng2
example : ∀ (i : fin 4), ⟦H2 ⬝ |11⟩⟧ i = 1/4 := by solve_rng2


/-
Copyright (c) 2020 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel, Scott Morrison
-/
import ring_theory.ideal.quotient
import ring_theory.principal_ideal_domain

/-!
# Invariant basis number property

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

We say that a ring `R` satisfies the invariant basis number property if there is a well-defined
notion of the rank of a finitely generated free (left) `R`-module. Since a finitely generated free
module with a basis consisting of `n` elements is linearly equivalent to `fin n → R`, it is
sufficient that `(fin n → R) ≃ₗ[R] (fin m → R)` implies `n = m`.

It is also useful to consider two stronger conditions, namely the rank condition,
that a surjective linear map `(fin n → R) →ₗ[R] (fin m → R)` implies `m ≤ n` and
the strong rank condition, that an injective linear map `(fin n → R) →ₗ[R] (fin m → R)`
implies `n ≤ m`.

The strong rank condition implies the rank condition, and the rank condition implies
the invariant basis number property.

## Main definitions

`strong_rank_condition R` is a type class stating that `R` satisfies the strong rank condition.
`rank_condition R` is a type class stating that `R` satisfies the rank condition.
`invariant_basis_number R` is a type class stating that `R` has the invariant basis number property.

## Main results

We show that every nontrivial left-noetherian ring satisfies the strong rank condition,
(and so in particular every division ring or field),
and then use this to show every nontrivial commutative ring has the invariant basis number property.

More generally, every commutative ring satisfies the strong rank condition. This is proved in
`linear_algebra/free_module/strong_rank_condition`. We keep
`invariant_basis_number_of_nontrivial_of_comm_ring` here since it imports fewer files.

## Future work

So far, there is no API at all for the `invariant_basis_number` class. There are several natural
ways to formulate that a module `M` is finitely generated and free, for example
`M ≃ₗ[R] (fin n → R)`, `M ≃ₗ[R] (ι → R)`, where `ι` is a fintype, or providing a basis indexed by
a finite type. There should be lemmas applying the invariant basis number property to each
situation.

The finite version of the invariant basis number property implies the infinite analogue, i.e., that
`(ι →₀ R) ≃ₗ[R] (ι' →₀ R)` implies that `cardinal.mk ι = cardinal.mk ι'`. This fact (and its
variants) should be formalized.

## References

* https://en.wikipedia.org/wiki/Invariant_basis_number
* https://mathoverflow.net/a/2574/

## Tags

free module, rank, invariant basis number, IBN

-/

noncomputable theory

open_locale classical big_operators
open function

universes u v w

section
variables (R : Type u) [semiring R]

/-- We say that `R` satisfies the strong rank condition if `(fin n → R) →ₗ[R] (fin m → R)` injective
    implies `n ≤ m`. -/
@[mk_iff]
class strong_rank_condition : Prop :=
(le_of_fin_injective : ∀ {n m : ℕ} (f : (fin n → R) →ₗ[R] (fin m → R)), injective f → n ≤ m)

lemma le_of_fin_injective [strong_rank_condition R] {n m : ℕ} (f : (fin n → R) →ₗ[R] (fin m → R)) :
  injective f → n ≤ m :=
strong_rank_condition.le_of_fin_injective f

/-- A ring satisfies the strong rank condition if and only if, for all `n : ℕ`, any linear map
`(fin (n + 1) → R) →ₗ[R] (fin n → R)` is not injective. -/
lemma strong_rank_condition_iff_succ : strong_rank_condition R ↔
  ∀ (n : ℕ) (f : (fin (n + 1) → R) →ₗ[R] (fin n → R)), ¬function.injective f :=
begin
  refine ⟨λ h n, λ f hf, _, λ h, ⟨λ n m f hf, _⟩⟩,
  { letI : strong_rank_condition R := h,
    exact nat.not_succ_le_self n (le_of_fin_injective R f hf) },
  { by_contra H,
    exact h m (f.comp (function.extend_by_zero.linear_map R (fin.cast_le (not_le.1 H))))
      (hf.comp (function.extend_injective (rel_embedding.injective _) 0)) }
end

lemma card_le_of_injective [strong_rank_condition R] {α β : Type*} [fintype α] [fintype β]
  (f : (α → R) →ₗ[R] (β → R)) (i : injective f) : fintype.card α ≤ fintype.card β :=
begin
  let P := linear_equiv.fun_congr_left R R (fintype.equiv_fin α),
  let Q := linear_equiv.fun_congr_left R R (fintype.equiv_fin β),
  exact le_of_fin_injective R ((Q.symm.to_linear_map.comp f).comp P.to_linear_map)
    (((linear_equiv.symm Q).injective.comp i).comp (linear_equiv.injective P)),
end

lemma card_le_of_injective' [strong_rank_condition R] {α β : Type*} [fintype α] [fintype β]
  (f : (α →₀ R) →ₗ[R] (β →₀ R)) (i : injective f) : fintype.card α ≤ fintype.card β :=
begin
  let P := (finsupp.linear_equiv_fun_on_finite R R β),
  let Q := (finsupp.linear_equiv_fun_on_finite R R α).symm,
  exact card_le_of_injective R ((P.to_linear_map.comp f).comp Q.to_linear_map)
    ((P.injective.comp i).comp Q.injective)
end

/-- We say that `R` satisfies the rank condition if `(fin n → R) →ₗ[R] (fin m → R)` surjective
    implies `m ≤ n`. -/
class rank_condition : Prop :=
(le_of_fin_surjective : ∀ {n m : ℕ} (f : (fin n → R) →ₗ[R] (fin m → R)), surjective f → m ≤ n)

lemma le_of_fin_surjective [rank_condition R] {n m : ℕ} (f : (fin n → R) →ₗ[R] (fin m → R)) :
  surjective f → m ≤ n :=
rank_condition.le_of_fin_surjective f

lemma card_le_of_surjective [rank_condition R] {α β : Type*} [fintype α] [fintype β]
  (f : (α → R) →ₗ[R] (β → R)) (i : surjective f) : fintype.card β ≤ fintype.card α :=
begin
  let P := linear_equiv.fun_congr_left R R (fintype.equiv_fin α),
  let Q := linear_equiv.fun_congr_left R R (fintype.equiv_fin β),
  exact le_of_fin_surjective R ((Q.symm.to_linear_map.comp f).comp P.to_linear_map)
    (((linear_equiv.symm Q).surjective.comp i).comp (linear_equiv.surjective P)),
end

lemma card_le_of_surjective' [rank_condition R] {α β : Type*} [fintype α] [fintype β]
  (f : (α →₀ R) →ₗ[R] (β →₀ R)) (i : surjective f) : fintype.card β ≤ fintype.card α :=
begin
  let P := (finsupp.linear_equiv_fun_on_finite R R β),
  let Q := (finsupp.linear_equiv_fun_on_finite R R α).symm,
  exact card_le_of_surjective R ((P.to_linear_map.comp f).comp Q.to_linear_map)
    ((P.surjective.comp i).comp Q.surjective)
end

/--
By the universal property for free modules, any surjective map `(fin n → R) →ₗ[R] (fin m → R)`
has an injective splitting `(fin m → R) →ₗ[R] (fin n → R)`
from which the strong rank condition gives the necessary inequality for the rank condition.
-/
@[priority 100]
instance rank_condition_of_strong_rank_condition [strong_rank_condition R] : rank_condition R :=
{ le_of_fin_surjective := λ n m f s,
    le_of_fin_injective R _ (f.splitting_of_fun_on_fintype_surjective_injective s), }

/-- We say that `R` has the invariant basis number property if `(fin n → R) ≃ₗ[R] (fin m → R)`
    implies `n = m`. This gives rise to a well-defined notion of rank of a finitely generated free
    module. -/
class invariant_basis_number : Prop :=
(eq_of_fin_equiv : ∀ {n m : ℕ}, ((fin n → R) ≃ₗ[R] (fin m → R)) → n = m)

@[priority 100]
instance invariant_basis_number_of_rank_condition [rank_condition R] : invariant_basis_number R :=
{ eq_of_fin_equiv := λ n m e, le_antisymm
    (le_of_fin_surjective R e.symm.to_linear_map e.symm.surjective)
    (le_of_fin_surjective R e.to_linear_map e.surjective) }

end

section
variables (R : Type u) [semiring R] [invariant_basis_number R]

lemma eq_of_fin_equiv {n m : ℕ} : ((fin n → R) ≃ₗ[R] (fin m → R)) → n = m :=
invariant_basis_number.eq_of_fin_equiv

lemma card_eq_of_lequiv {α β : Type*} [fintype α] [fintype β]
  (f : (α → R) ≃ₗ[R] (β → R)) : fintype.card α = fintype.card β :=
eq_of_fin_equiv R (((linear_equiv.fun_congr_left R R (fintype.equiv_fin α)).trans f) ≪≫ₗ
  ((linear_equiv.fun_congr_left R R (fintype.equiv_fin β)).symm))

lemma nontrivial_of_invariant_basis_number : nontrivial R :=
begin
  by_contra h,
  refine zero_ne_one (eq_of_fin_equiv R _),
  haveI := not_nontrivial_iff_subsingleton.1 h,
  haveI : subsingleton (fin 1 → R) := ⟨λ a b, funext $ λ x, subsingleton.elim _ _⟩,
  refine { .. }; { intros, exact 0 } <|> tidy
end

end

section
variables (R : Type u) [ring R] [nontrivial R] [is_noetherian_ring R]

/--
Any nontrivial noetherian ring satisfies the strong rank condition.

An injective map `((fin n ⊕ fin (1 + m)) → R) →ₗ[R] (fin n → R)` for some left-noetherian `R`
would force `fin (1 + m) → R ≃ₗ punit` (via `is_noetherian.equiv_punit_of_prod_injective`),
which is not the case!
-/
-- Note this includes fields,
-- and we use this below to show any commutative ring has invariant basis number.
@[priority 100]
instance noetherian_ring_strong_rank_condition : strong_rank_condition R :=
begin
  fsplit,
  intros m n f i,
  by_contradiction h,
  rw [not_le, ←nat.add_one_le_iff, le_iff_exists_add] at h,
  obtain ⟨m, rfl⟩ := h,
  let e : fin (n + 1 + m) ≃ fin n ⊕ fin (1 + m) :=
    (fin_congr (add_assoc _ _ _)).trans fin_sum_fin_equiv.symm,
  let f' := f.comp ((linear_equiv.sum_arrow_lequiv_prod_arrow _ _ R R).symm.trans
    (linear_equiv.fun_congr_left R R e)).to_linear_map,
  have i' : injective f' := i.comp (linear_equiv.injective _),
  apply @zero_ne_one (fin (1 + m) → R) _ _,
  apply (is_noetherian.equiv_punit_of_prod_injective f' i').injective,
  ext,
end

end

/-!
  We want to show that nontrivial commutative rings have invariant basis number. The idea is to
  take a maximal ideal `I` of `R` and use an isomorphism `R^n ≃ R^m` of `R` modules to produce an
  isomorphism `(R/I)^n ≃ (R/I)^m` of `R/I`-modules, which will imply `n = m` since `R/I` is a field
  and we know that fields have invariant basis number.

  We construct the isomorphism in two steps:
  1. We construct the ring `R^n/I^n`, show that it is an `R/I`-module and show that there is an
     isomorphism of `R/I`-modules `R^n/I^n ≃ (R/I)^n`. This isomorphism is called
    `ideal.pi_quot_equiv` and is located in the file `ring_theory/ideals.lean`.
  2. We construct an isomorphism of `R/I`-modules `R^n/I^n ≃ R^m/I^m` using the isomorphism
     `R^n ≃ R^m`.
-/

section
variables {R : Type u} [comm_ring R] (I : ideal R) {ι : Type v} [fintype ι] {ι' : Type w}

/-- An `R`-linear map `R^n → R^m` induces a function `R^n/I^n → R^m/I^m`. -/
private def induced_map (I : ideal R) (e : (ι → R) →ₗ[R] (ι' → R)) :
  (ι → R) ⧸ (I.pi ι) → (ι' → R) ⧸ I.pi ι' :=
λ x, quotient.lift_on' x (λ y, ideal.quotient.mk _ (e y))
begin
  refine λ a b hab, ideal.quotient.eq.2 (λ h, _),
  rw submodule.quotient_rel_r_def at hab,
  rw ←linear_map.map_sub,
  exact ideal.map_pi _ _ hab e h,
end

/-- An isomorphism of `R`-modules `R^n ≃ R^m` induces an isomorphism of `R/I`-modules
    `R^n/I^n ≃ R^m/I^m`. -/
private def induced_equiv [fintype ι'] (I : ideal R) (e : (ι → R) ≃ₗ[R] (ι' → R)) :
  ((ι → R) ⧸ I.pi ι) ≃ₗ[R ⧸ I] (ι' → R) ⧸ I.pi ι' :=
begin
  refine { to_fun := induced_map I e, inv_fun := induced_map I e.symm, .. },
  all_goals { rintro ⟨a⟩ ⟨b⟩ <|> rintro ⟨a⟩,
    convert_to ideal.quotient.mk _ _ = ideal.quotient.mk _ _,
    congr,
    simp only [map_add, linear_equiv.coe_coe, linear_equiv.map_smulₛₗ, ring_hom.id_apply,
               linear_equiv.symm_apply_apply, linear_equiv.apply_symm_apply] }
end

end

section
local attribute [instance] ideal.quotient.field

/-- Nontrivial commutative rings have the invariant basis number property.

In fact, any nontrivial commutative ring satisfies the strong rank condition, see
`comm_ring_strong_rank_condition`. We prove this instance separately to avoid dependency on
`linear_algebra.charpoly.basic`. -/
@[priority 100]
instance invariant_basis_number_of_nontrivial_of_comm_ring {R : Type u} [comm_ring R]
  [nontrivial R] : invariant_basis_number R :=
⟨λ n m e, let ⟨I, hI⟩ := ideal.exists_maximal R in
  by exactI eq_of_fin_equiv (R ⧸ I)
    ((ideal.pi_quot_equiv _ _).symm ≪≫ₗ ((induced_equiv _ e) ≪≫ₗ (ideal.pi_quot_equiv _ _)))⟩

end