/-
Copyright (c) 2015 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default

universes u v w

namespace Mathlib

def stream (α : Type u) := ℕ → α

namespace stream

def cons {α : Type u} (a : α) (s : stream α) : stream α := fun (i : ℕ) => sorry

infixr:67 " :: " => Mathlib.stream.cons

def head {α : Type u} (s : stream α) : α := s 0

def tail {α : Type u} (s : stream α) : stream α := fun (i : ℕ) => s (i + 1)

def drop {α : Type u} (n : ℕ) (s : stream α) : stream α := fun (i : ℕ) => s (i + n)

def nth {α : Type u} (n : ℕ) (s : stream α) : α := s n

protected theorem eta {α : Type u} (s : stream α) : head s :: tail s = s :=
  funext
    fun (i : ℕ) =>
      nat.cases_on i (Eq.refl (cons (head s) (tail s) 0))
        fun (i : ℕ) => Eq.refl (cons (head s) (tail s) (Nat.succ i))

theorem nth_zero_cons {α : Type u} (a : α) (s : stream α) : nth 0 (a :: s) = a := rfl

theorem head_cons {α : Type u} (a : α) (s : stream α) : head (a :: s) = a := rfl

theorem tail_cons {α : Type u} (a : α) (s : stream α) : tail (a :: s) = s := rfl

theorem tail_drop {α : Type u} (n : ℕ) (s : stream α) : tail (drop n s) = drop n (tail s) := sorry

theorem nth_drop {α : Type u} (n : ℕ) (m : ℕ) (s : stream α) : nth n (drop m s) = nth (n + m) s :=
  rfl

theorem tail_eq_drop {α : Type u} (s : stream α) : tail s = drop 1 s := rfl

theorem drop_drop {α : Type u} (n : ℕ) (m : ℕ) (s : stream α) :
    drop n (drop m s) = drop (n + m) s :=
  sorry

theorem nth_succ {α : Type u} (n : ℕ) (s : stream α) : nth (Nat.succ n) s = nth n (tail s) := rfl

theorem drop_succ {α : Type u} (n : ℕ) (s : stream α) : drop (Nat.succ n) s = drop n (tail s) := rfl

protected theorem ext {α : Type u} {s₁ : stream α} {s₂ : stream α} :
    (∀ (n : ℕ), nth n s₁ = nth n s₂) → s₁ = s₂ :=
  fun (h : ∀ (n : ℕ), nth n s₁ = nth n s₂) => funext h

def all {α : Type u} (p : α → Prop) (s : stream α) := ∀ (n : ℕ), p (nth n s)

def any {α : Type u} (p : α → Prop) (s : stream α) := ∃ (n : ℕ), p (nth n s)

theorem all_def {α : Type u} (p : α → Prop) (s : stream α) : all p s = ∀ (n : ℕ), p (nth n s) := rfl

theorem any_def {α : Type u} (p : α → Prop) (s : stream α) : any p s = ∃ (n : ℕ), p (nth n s) := rfl

protected def mem {α : Type u} (a : α) (s : stream α) := any (fun (b : α) => a = b) s

protected instance has_mem {α : Type u} : has_mem α (stream α) := has_mem.mk stream.mem

theorem mem_cons {α : Type u} (a : α) (s : stream α) : a ∈ a :: s := exists.intro 0 rfl

theorem mem_cons_of_mem {α : Type u} {a : α} {s : stream α} (b : α) : a ∈ s → a ∈ b :: s := sorry

theorem eq_or_mem_of_mem_cons {α : Type u} {a : α} {b : α} {s : stream α} :
    a ∈ b :: s → a = b ∨ a ∈ s :=
  sorry

theorem mem_of_nth_eq {α : Type u} {n : ℕ} {s : stream α} {a : α} : a = nth n s → a ∈ s :=
  fun (h : a = nth n s) => exists.intro n h

def map {α : Type u} {β : Type v} (f : α → β) (s : stream α) : stream β :=
  fun (n : ℕ) => f (nth n s)

theorem drop_map {α : Type u} {β : Type v} (f : α → β) (n : ℕ) (s : stream α) :
    drop n (map f s) = map f (drop n s) :=
  stream.ext fun (i : ℕ) => rfl

theorem nth_map {α : Type u} {β : Type v} (f : α → β) (n : ℕ) (s : stream α) :
    nth n (map f s) = f (nth n s) :=
  rfl

theorem tail_map {α : Type u} {β : Type v} (f : α → β) (s : stream α) :
    tail (map f s) = map f (tail s) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (tail (map f s) = map f (tail s))) (tail_eq_drop (map f s))))
    (Eq.refl (drop 1 (map f s)))

theorem head_map {α : Type u} {β : Type v} (f : α → β) (s : stream α) :
    head (map f s) = f (head s) :=
  rfl

theorem map_eq {α : Type u} {β : Type v} (f : α → β) (s : stream α) :
    map f s = f (head s) :: map f (tail s) :=
  sorry

theorem map_cons {α : Type u} {β : Type v} (f : α → β) (a : α) (s : stream α) :
    map f (a :: s) = f a :: map f s :=
  sorry

theorem map_id {α : Type u} (s : stream α) : map id s = s := rfl

theorem map_map {α : Type u} {β : Type v} {δ : Type w} (g : β → δ) (f : α → β) (s : stream α) :
    map g (map f s) = map (g ∘ f) s :=
  rfl

theorem map_tail {α : Type u} {β : Type v} (f : α → β) (s : stream α) :
    map f (tail s) = tail (map f s) :=
  rfl

theorem mem_map {α : Type u} {β : Type v} (f : α → β) {a : α} {s : stream α} :
    a ∈ s → f a ∈ map f s :=
  sorry

theorem exists_of_mem_map {α : Type u} {β : Type v} {f : α → β} {b : β} {s : stream α} :
    b ∈ map f s → ∃ (a : α), a ∈ s ∧ f a = b :=
  sorry

def zip {α : Type u} {β : Type v} {δ : Type w} (f : α → β → δ) (s₁ : stream α) (s₂ : stream β) :
    stream δ :=
  fun (n : ℕ) => f (nth n s₁) (nth n s₂)

theorem drop_zip {α : Type u} {β : Type v} {δ : Type w} (f : α → β → δ) (n : ℕ) (s₁ : stream α)
    (s₂ : stream β) : drop n (zip f s₁ s₂) = zip f (drop n s₁) (drop n s₂) :=
  stream.ext fun (i : ℕ) => rfl

theorem nth_zip {α : Type u} {β : Type v} {δ : Type w} (f : α → β → δ) (n : ℕ) (s₁ : stream α)
    (s₂ : stream β) : nth n (zip f s₁ s₂) = f (nth n s₁) (nth n s₂) :=
  rfl

theorem head_zip {α : Type u} {β : Type v} {δ : Type w} (f : α → β → δ) (s₁ : stream α)
    (s₂ : stream β) : head (zip f s₁ s₂) = f (head s₁) (head s₂) :=
  rfl

theorem tail_zip {α : Type u} {β : Type v} {δ : Type w} (f : α → β → δ) (s₁ : stream α)
    (s₂ : stream β) : tail (zip f s₁ s₂) = zip f (tail s₁) (tail s₂) :=
  rfl

theorem zip_eq {α : Type u} {β : Type v} {δ : Type w} (f : α → β → δ) (s₁ : stream α)
    (s₂ : stream β) : zip f s₁ s₂ = f (head s₁) (head s₂) :: zip f (tail s₁) (tail s₂) :=
  sorry

def const {α : Type u} (a : α) : stream α := fun (n : ℕ) => a

theorem mem_const {α : Type u} (a : α) : a ∈ const a := exists.intro 0 rfl

theorem const_eq {α : Type u} (a : α) : const a = a :: const a :=
  stream.ext
    fun (n : ℕ) =>
      nat.cases_on n (Eq.refl (nth 0 (const a))) fun (n : ℕ) => Eq.refl (nth (Nat.succ n) (const a))

theorem tail_const {α : Type u} (a : α) : tail (const a) = const a :=
  (fun (this : tail (a :: const a) = const a) =>
      eq.mp (Eq._oldrec (Eq.refl (tail (a :: const a) = const a)) (Eq.symm (const_eq a))) this)
    rfl

theorem map_const {α : Type u} {β : Type v} (f : α → β) (a : α) : map f (const a) = const (f a) :=
  rfl

theorem nth_const {α : Type u} (n : ℕ) (a : α) : nth n (const a) = a := rfl

theorem drop_const {α : Type u} (n : ℕ) (a : α) : drop n (const a) = const a :=
  stream.ext fun (i : ℕ) => rfl

def iterate {α : Type u} (f : α → α) (a : α) : stream α :=
  fun (n : ℕ) => nat.rec_on n a fun (n : ℕ) (r : α) => f r

theorem head_iterate {α : Type u} (f : α → α) (a : α) : head (iterate f a) = a := rfl

theorem tail_iterate {α : Type u} (f : α → α) (a : α) : tail (iterate f a) = iterate f (f a) :=
  sorry

theorem iterate_eq {α : Type u} (f : α → α) (a : α) : iterate f a = a :: iterate f (f a) := sorry

theorem nth_zero_iterate {α : Type u} (f : α → α) (a : α) : nth 0 (iterate f a) = a := rfl

theorem nth_succ_iterate {α : Type u} (n : ℕ) (f : α → α) (a : α) :
    nth (Nat.succ n) (iterate f a) = nth n (iterate f (f a)) :=
  eq.mpr
    (id
      (Eq._oldrec (Eq.refl (nth (Nat.succ n) (iterate f a) = nth n (iterate f (f a))))
        (nth_succ n (iterate f a))))
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl (nth n (tail (iterate f a)) = nth n (iterate f (f a))))
          (tail_iterate f a)))
      (Eq.refl (nth n (iterate f (f a)))))

def is_bisimulation {α : Type u} (R : stream α → stream α → Prop) :=
  ∀ {s₁ s₂ : stream α}, R s₁ s₂ → head s₁ = head s₂ ∧ R (tail s₁) (tail s₂)

theorem nth_of_bisim {α : Type u} (R : stream α → stream α → Prop) (bisim : is_bisimulation R)
    {s₁ : stream α} {s₂ : stream α} (n : ℕ) :
    R s₁ s₂ → nth n s₁ = nth n s₂ ∧ R (drop (n + 1) s₁) (drop (n + 1) s₂) :=
  sorry

theorem eq_of_bisim {α : Type u} (R : stream α → stream α → Prop) (bisim : is_bisimulation R)
    {s₁ : stream α} {s₂ : stream α} : R s₁ s₂ → s₁ = s₂ :=
  fun (r : R s₁ s₂) => stream.ext fun (n : ℕ) => and.elim_left (nth_of_bisim R bisim n r)

theorem bisim_simple {α : Type u} (s₁ : stream α) (s₂ : stream α) :
    head s₁ = head s₂ → s₁ = tail s₁ → s₂ = tail s₂ → s₁ = s₂ :=
  sorry

theorem coinduction {α : Type u} {s₁ : stream α} {s₂ : stream α} :
    head s₁ = head s₂ →
        (∀ (β : Type u) (fr : stream α → β), fr s₁ = fr s₂ → fr (tail s₁) = fr (tail s₂)) →
          s₁ = s₂ :=
  sorry

theorem iterate_id {α : Type u} (a : α) : iterate id a = const a := sorry

theorem map_iterate {α : Type u} (f : α → α) (a : α) : iterate f (f a) = map f (iterate f a) :=
  sorry

def corec {α : Type u} {β : Type v} (f : α → β) (g : α → α) : α → stream β :=
  fun (a : α) => map f (iterate g a)

def corec_on {α : Type u} {β : Type v} (a : α) (f : α → β) (g : α → α) : stream β := corec f g a

theorem corec_def {α : Type u} {β : Type v} (f : α → β) (g : α → α) (a : α) :
    corec f g a = map f (iterate g a) :=
  rfl

theorem corec_eq {α : Type u} {β : Type v} (f : α → β) (g : α → α) (a : α) :
    corec f g a = f a :: corec f g (g a) :=
  sorry

theorem corec_id_id_eq_const {α : Type u} (a : α) : corec id id a = const a :=
  eq.mpr (id (Eq._oldrec (Eq.refl (corec id id a = const a)) (corec_def id id a)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (map id (iterate id a) = const a)) (map_id (iterate id a))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (iterate id a = const a)) (iterate_id a)))
        (Eq.refl (const a))))

theorem corec_id_f_eq_iterate {α : Type u} (f : α → α) (a : α) : corec id f a = iterate f a := rfl

def corec' {α : Type u} {β : Type v} (f : α → β × α) : α → stream β :=
  corec (prod.fst ∘ f) (prod.snd ∘ f)

theorem corec'_eq {α : Type u} {β : Type v} (f : α → β × α) (a : α) :
    corec' f a = prod.fst (f a) :: corec' f (prod.snd (f a)) :=
  corec_eq (prod.fst ∘ f) (prod.snd ∘ f) a

-- corec is also known as unfold

def unfolds {α : Type u} {β : Type v} (g : α → β) (f : α → α) (a : α) : stream β := corec g f a

theorem unfolds_eq {α : Type u} {β : Type v} (g : α → β) (f : α → α) (a : α) :
    unfolds g f a = g a :: unfolds g f (f a) :=
  sorry

theorem nth_unfolds_head_tail {α : Type u} (n : ℕ) (s : stream α) :
    nth n (unfolds head tail s) = nth n s :=
  sorry

theorem unfolds_head_eq {α : Type u} (s : stream α) : unfolds head tail s = s :=
  stream.ext fun (n : ℕ) => nth_unfolds_head_tail n s

def interleave {α : Type u} (s₁ : stream α) (s₂ : stream α) : stream α :=
  corec_on (s₁, s₂) (fun (_x : stream α × stream α) => sorry)
    fun (_x : stream α × stream α) => sorry

infixl:65 "⋈" => Mathlib.stream.interleave

theorem interleave_eq {α : Type u} (s₁ : stream α) (s₂ : stream α) :
    s₁⋈s₂ = head s₁ :: head s₂ :: (tail s₁⋈tail s₂) :=
  sorry

theorem tail_interleave {α : Type u} (s₁ : stream α) (s₂ : stream α) : tail (s₁⋈s₂) = s₂⋈tail s₁ :=
  sorry

theorem interleave_tail_tail {α : Type u} (s₁ : stream α) (s₂ : stream α) :
    tail s₁⋈tail s₂ = tail (tail (s₁⋈s₂)) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (tail s₁⋈tail s₂ = tail (tail (s₁⋈s₂)))) (interleave_eq s₁ s₂)))
    (Eq.refl (tail s₁⋈tail s₂))

theorem nth_interleave_left {α : Type u} (n : ℕ) (s₁ : stream α) (s₂ : stream α) :
    nth (bit0 1 * n) (s₁⋈s₂) = nth n s₁ :=
  sorry

theorem nth_interleave_right {α : Type u} (n : ℕ) (s₁ : stream α) (s₂ : stream α) :
    nth (bit0 1 * n + 1) (s₁⋈s₂) = nth n s₂ :=
  sorry

theorem mem_interleave_left {α : Type u} {a : α} {s₁ : stream α} (s₂ : stream α) :
    a ∈ s₁ → a ∈ s₁⋈s₂ :=
  sorry

theorem mem_interleave_right {α : Type u} {a : α} {s₁ : stream α} (s₂ : stream α) :
    a ∈ s₂ → a ∈ s₁⋈s₂ :=
  sorry

def even {α : Type u} (s : stream α) : stream α :=
  corec (fun (s : stream α) => head s) (fun (s : stream α) => tail (tail s)) s

def odd {α : Type u} (s : stream α) : stream α := even (tail s)

theorem odd_eq {α : Type u} (s : stream α) : odd s = even (tail s) := rfl

theorem head_even {α : Type u} (s : stream α) : head (even s) = head s := rfl

theorem tail_even {α : Type u} (s : stream α) : tail (even s) = even (tail (tail s)) := sorry

theorem even_cons_cons {α : Type u} (a₁ : α) (a₂ : α) (s : stream α) :
    even (a₁ :: a₂ :: s) = a₁ :: even s :=
  sorry

theorem even_tail {α : Type u} (s : stream α) : even (tail s) = odd s := rfl

theorem even_interleave {α : Type u} (s₁ : stream α) (s₂ : stream α) : even (s₁⋈s₂) = s₁ := sorry

theorem interleave_even_odd {α : Type u} (s₁ : stream α) : even s₁⋈odd s₁ = s₁ := sorry

theorem nth_even {α : Type u} (n : ℕ) (s : stream α) : nth n (even s) = nth (bit0 1 * n) s := sorry

theorem nth_odd {α : Type u} (n : ℕ) (s : stream α) : nth n (odd s) = nth (bit0 1 * n + 1) s :=
  eq.mpr (id (Eq._oldrec (Eq.refl (nth n (odd s) = nth (bit0 1 * n + 1) s)) (odd_eq s)))
    (eq.mpr
      (id
        (Eq._oldrec (Eq.refl (nth n (even (tail s)) = nth (bit0 1 * n + 1) s))
          (nth_even n (tail s))))
      (Eq.refl (nth (bit0 1 * n) (tail s))))

theorem mem_of_mem_even {α : Type u} (a : α) (s : stream α) : a ∈ even s → a ∈ s := sorry

theorem mem_of_mem_odd {α : Type u} (a : α) (s : stream α) : a ∈ odd s → a ∈ s := sorry

def append_stream {α : Type u} : List α → stream α → stream α := sorry

theorem nil_append_stream {α : Type u} (s : stream α) : append_stream [] s = s := rfl

theorem cons_append_stream {α : Type u} (a : α) (l : List α) (s : stream α) :
    append_stream (a :: l) s = a :: append_stream l s :=
  rfl

infixl:65 "++ₛ" => Mathlib.stream.append_stream

theorem append_append_stream {α : Type u} (l₁ : List α) (l₂ : List α) (s : stream α) :
    l₁ ++ l₂++ₛs = l₁++ₛ(l₂++ₛs) :=
  sorry

theorem map_append_stream {α : Type u} {β : Type v} (f : α → β) (l : List α) (s : stream α) :
    map f (l++ₛs) = list.map f l++ₛmap f s :=
  sorry

theorem drop_append_stream {α : Type u} (l : List α) (s : stream α) :
    drop (list.length l) (l++ₛs) = s :=
  sorry

theorem append_stream_head_tail {α : Type u} (s : stream α) : [head s]++ₛtail s = s :=
  eq.mpr
    (id (Eq._oldrec (Eq.refl ([head s]++ₛtail s = s)) (cons_append_stream (head s) [] (tail s))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (head s :: ([]++ₛtail s) = s)) (nil_append_stream (tail s))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (head s :: tail s = s)) (stream.eta s))) (Eq.refl s)))

theorem mem_append_stream_right {α : Type u} {a : α} (l : List α) {s : stream α} :
    a ∈ s → a ∈ l++ₛs :=
  sorry

theorem mem_append_stream_left {α : Type u} {a : α} {l : List α} (s : stream α) :
    a ∈ l → a ∈ l++ₛs :=
  sorry

def approx {α : Type u} : ℕ → stream α → List α := sorry

theorem approx_zero {α : Type u} (s : stream α) : approx 0 s = [] := rfl

theorem approx_succ {α : Type u} (n : ℕ) (s : stream α) :
    approx (Nat.succ n) s = head s :: approx n (tail s) :=
  rfl

theorem nth_approx {α : Type u} (n : ℕ) (s : stream α) :
    list.nth (approx (Nat.succ n) s) n = some (nth n s) :=
  sorry

theorem append_approx_drop {α : Type u} (n : ℕ) (s : stream α) : approx n s++ₛdrop n s = s := sorry

-- Take theorem reduces a proof of equality of infinite streams to an

-- induction over all their finite approximations.

theorem take_theorem {α : Type u} (s₁ : stream α) (s₂ : stream α) :
    (∀ (n : ℕ), approx n s₁ = approx n s₂) → s₁ = s₂ :=
  sorry

-- auxiliary def for cycle corecursive def

-- auxiliary def for cycle corecursive def

def cycle {α : Type u} (l : List α) : l ≠ [] → stream α := sorry

theorem cycle_eq {α : Type u} (l : List α) (h : l ≠ []) : cycle l h = l++ₛcycle l h := sorry

theorem mem_cycle {α : Type u} {a : α} {l : List α} (h : l ≠ []) : a ∈ l → a ∈ cycle l h :=
  fun (ainl : a ∈ l) =>
    eq.mpr (id (Eq._oldrec (Eq.refl (a ∈ cycle l h)) (cycle_eq l h)))
      (mem_append_stream_left (cycle l h) ainl)

theorem cycle_singleton {α : Type u} (a : α) (h : [a] ≠ []) : cycle [a] h = const a := sorry

def tails {α : Type u} (s : stream α) : stream (stream α) := corec id tail (tail s)

theorem tails_eq {α : Type u} (s : stream α) : tails s = tail s :: tails (tail s) := sorry

theorem nth_tails {α : Type u} (n : ℕ) (s : stream α) : nth n (tails s) = drop n (tail s) := sorry

theorem tails_eq_iterate {α : Type u} (s : stream α) : tails s = iterate tail (tail s) := rfl

def inits_core {α : Type u} (l : List α) (s : stream α) : stream (List α) :=
  corec_on (l, s) (fun (_x : List α × stream α) => sorry) fun (p : List α × stream α) => sorry

def inits {α : Type u} (s : stream α) : stream (List α) := inits_core [head s] (tail s)

theorem inits_core_eq {α : Type u} (l : List α) (s : stream α) :
    inits_core l s = l :: inits_core (l ++ [head s]) (tail s) :=
  sorry

theorem tail_inits {α : Type u} (s : stream α) :
    tail (inits s) = inits_core [head s, head (tail s)] (tail (tail s)) :=
  sorry

theorem inits_tail {α : Type u} (s : stream α) :
    inits (tail s) = inits_core [head (tail s)] (tail (tail s)) :=
  rfl

theorem cons_nth_inits_core {α : Type u} (a : α) (n : ℕ) (l : List α) (s : stream α) :
    a :: nth n (inits_core l s) = nth n (inits_core (a :: l) s) :=
  sorry

theorem nth_inits {α : Type u} (n : ℕ) (s : stream α) : nth n (inits s) = approx (Nat.succ n) s :=
  sorry

theorem inits_eq {α : Type u} (s : stream α) :
    inits s = [head s] :: map (List.cons (head s)) (inits (tail s)) :=
  sorry

theorem zip_inits_tails {α : Type u} (s : stream α) :
    zip append_stream (inits s) (tails s) = const s :=
  sorry

def pure {α : Type u} (a : α) : stream α := const a

def apply {α : Type u} {β : Type v} (f : stream (α → β)) (s : stream α) : stream β :=
  fun (n : ℕ) => nth n f (nth n s)

infixl:75 "⊛" => Mathlib.stream.apply

theorem identity {α : Type u} (s : stream α) : pure id⊛s = s := rfl

theorem composition {α : Type u} {β : Type v} {δ : Type w} (g : stream (β → δ)) (f : stream (α → β))
    (s : stream α) : pure function.comp⊛g⊛f⊛s = g⊛(f⊛s) :=
  rfl

theorem homomorphism {α : Type u} {β : Type v} (f : α → β) (a : α) : pure f⊛pure a = pure (f a) :=
  rfl

theorem interchange {α : Type u} {β : Type v} (fs : stream (α → β)) (a : α) :
    fs⊛pure a = (pure fun (f : α → β) => f a)⊛fs :=
  rfl

theorem map_eq_apply {α : Type u} {β : Type v} (f : α → β) (s : stream α) : map f s = pure f⊛s :=
  rfl

def nats : stream ℕ := fun (n : ℕ) => n

theorem nth_nats (n : ℕ) : nth n nats = n := rfl

theorem nats_eq : nats = 0 :: map Nat.succ nats := sorry

end Mathlib


import algebra.archimedean data.set.intervals order.conditionally_complete_lattice

namespace set

protected def dite (c : Prop) {α : Type*} (pos : c → set α) (neg : ¬c → set α) : set α :=
{ x | (∃ hc, x ∈ pos hc) ∨ (∃ hnc, x ∈ neg hnc) }

protected def ite (c : Prop) {α : Type*} (pos neg : set α) : set α :=
set.dite c (λ _, pos) (λ _, neg)

protected theorem dif_pos {c : Prop} (hc : c) {α : Type*} {pos neg} : (set.dite c pos neg : set α) = pos hc :=
ext $ λ z, ⟨λ hz, or.cases_on hz Exists.snd $ λ ⟨hnc, _⟩, absurd hc hnc,
λ hz, or.inl ⟨hc, hz⟩⟩

protected theorem dif_neg {c : Prop} (hnc : ¬c) {α : Type*} {pos neg} : (set.dite c pos neg : set α) = neg hnc :=
ext $ λ z, ⟨λ hz, or.cases_on hz (λ ⟨hc, _⟩, absurd hc hnc) Exists.snd,
λ hz, or.inr ⟨hnc, hz⟩⟩

protected theorem if_pos {c : Prop} (hc : c) {α : Type*} {t e} : (set.ite c t e : set α) = t :=
set.dif_pos hc

protected theorem if_neg {c : Prop} (hnc : ¬c) {α : Type*} {t e} : (set.ite c t e : set α) = e :=
set.dif_neg hnc

end set

open set lattice

structure real : Type :=
(carrier : set ℚ)
(exists_carrier : ∃ x, x ∈ carrier)
(exists_not_carrier : ∃ x, x ∉ carrier)
(mem_of_mem_of_le : ∀ q ∈ carrier, ∀ p ≤ q, p ∈ carrier)
(exists_lt_of_mem : ∀ q ∈ carrier, ∃ M ∈ carrier, q < M)

notation `ℝ` := real

namespace real

@[extensionality]
theorem ext : ∀ {c₁ c₂ : ℝ}, c₁.carrier = c₂.carrier → c₁ = c₂
| ⟨_, _, _, _, _⟩ ⟨_, _, _, _, _⟩ rfl := rfl

protected def dite (c : Prop) (pos : c → ℝ) (neg : ¬c → ℝ) : ℝ :=
{ carrier := set.dite c (λ hc, (pos hc).1) (λ hnc, (neg hnc).1),
  exists_carrier := classical.by_cases
    (λ hc : c, by rw set.dif_pos hc; exact (pos hc).2)
    (λ hnc : ¬c, by rw set.dif_neg hnc; exact (neg hnc).2),
  exists_not_carrier := classical.by_cases
    (λ hc : c, by rw set.dif_pos hc; exact (pos hc).3)
    (λ hnc : ¬c, by rw set.dif_neg hnc; exact (neg hnc).3),
  mem_of_mem_of_le := classical.by_cases
    (λ hc : c, by rw set.dif_pos hc; exact (pos hc).4)
    (λ hnc : ¬c, by rw set.dif_neg hnc; exact (neg hnc).4),
  exists_lt_of_mem := classical.by_cases
    (λ hc : c, by rw set.dif_pos hc; exact (pos hc).5)
    (λ hnc : ¬c, by rw set.dif_neg hnc; exact (neg hnc).5) }

protected def ite (c : Prop) (pos neg : ℝ) : ℝ :=
real.dite c (λ _, pos) (λ _, neg)

protected theorem dif_pos {c : Prop} (hc : c) {pos neg} : real.dite c pos neg = pos hc :=
ext $ set.dif_pos hc

protected theorem dif_neg {c : Prop} (hnc : ¬c) {pos neg} : real.dite c pos neg = neg hnc :=
ext $ set.dif_neg hnc

protected theorem if_pos {c : Prop} (hc : c) {t e} : real.ite c t e = t :=
real.dif_pos hc

protected theorem if_neg {c : Prop} (hnc : ¬c) {t e} : real.ite c t e = e :=
real.dif_neg hnc

def of_rat (r : ℚ) : ℝ :=
⟨Iio r, ⟨r-1, sub_one_lt r⟩, ⟨r, lt_irrefl r⟩,
λ q hqr p hpq, lt_of_le_of_lt hpq hqr,
λ q hqr, ⟨q/2+r/2,
calc  q/2+r/2 < r/2+r/2 : add_lt_add_right (div_lt_div_of_lt_of_pos hqr two_pos) _
... = r : add_halves r,
calc  q = q/2+q/2 : (add_halves q).symm
... < q/2+r/2 : add_lt_add_left (div_lt_div_of_lt_of_pos hqr two_pos) _⟩⟩

instance : has_mem ℚ ℝ :=
⟨λ r c, r ∈ c.carrier⟩

theorem lt_of_mem_of_not_mem (c : ℝ) {p q : ℚ}
  (H1 : p ∈ c) (H2 : q ∉ c) : p < q :=
lt_of_not_ge $ mt (c.4 _ H1 _) H2

protected theorem le_total (c₁ c₂ : ℝ) : c₁.1 ⊆ c₂.1 ∨ c₂.1 ⊆ c₁.1 :=
classical.or_iff_not_imp_left.2 $ λ hc q hq2,
  let ⟨r, hr1, hr2⟩ := not_subset.1 hc in
  c₁.4 r hr1 _ $ le_of_lt $ lt_of_mem_of_not_mem c₂ hq2 hr2

protected def max (r₁ r₂ : ℝ) : ℝ :=
real.ite (r₁.1 ⊆ r₂.1) r₂ r₁

protected def min (r₁ r₂ : ℝ) : ℝ :=
real.ite (r₁.1 ⊆ r₂.1) r₁ r₂

protected def Sup (S : set ℝ) : ℝ :=
real.dite ((∃ r, r ∈ S) ∧ (∃ M : ℝ, ∀ r ∈ S, carrier r ⊆ M.1))
(λ hc, { carrier :=  ⋃ r ∈ S, carrier r,
  exists_carrier := let ⟨⟨r, hrs⟩, _⟩ := hc, ⟨q, hqr⟩ := r.2 in
    ⟨q, mem_bUnion hrs hqr⟩,
  exists_not_carrier := let ⟨_, M, HM⟩ := hc, ⟨q, hqM⟩ := M.3 in
    ⟨q, mt (λ H, bUnion_subset HM H) hqM⟩,
  mem_of_mem_of_le := λ q hq p hpq, let ⟨r, hrs, hqr⟩ := mem_bUnion_iff.1 hq in
    mem_bUnion hrs (r.4 q hqr _ hpq),
  exists_lt_of_mem := λ q hq, let ⟨r, hrs, hqr⟩ := mem_bUnion_iff.1 hq,
    ⟨M, HMr, hqM⟩ := r.5 q hqr in ⟨M, mem_bUnion hrs HMr, hqM⟩ })
(λ _, of_rat 0)

protected def Inf (S : set ℝ) : ℝ :=
real.Sup $ { m | ∀ r ∈ S, m.1 ⊆ carrier r }

instance conditionally_complete_linear_order : conditionally_complete_linear_order ℝ :=
{ le_total := real.le_total,
  sup := real.max,
  le_sup_left := λ r₁ r₂, show r₁.1 ⊆ (real.ite (r₁.1 ⊆ r₂.1) r₂ r₁).1, from classical.by_cases
    (λ h : r₁.1 ⊆ r₂.1, by rwa real.if_pos h) (λ h, by rw real.if_neg h),
  le_sup_right := λ r₁ r₂, show r₂.1 ⊆ (real.ite (r₁.1 ⊆ r₂.1) r₂ r₁).1, from classical.by_cases
    (λ h : r₁.1 ⊆ r₂.1, by rw real.if_pos h) (λ h, by rwa real.if_neg h; exact (real.le_total r₁ r₂).resolve_left h),
  sup_le := λ r₁ r₂ r hr1 hr2, show (real.ite (r₁.1 ⊆ r₂.1) r₂ r₁).1 ⊆ r.1, from classical.by_cases
    (λ h : r₁.1 ⊆ r₂.1, by rwa real.if_pos h) (λ h, by rwa real.if_neg h),
  inf := real.min,
  inf_le_left := λ r₁ r₂, show (real.ite (r₁.1 ⊆ r₂.1) r₁ r₂).1 ⊆ r₁.1, from classical.by_cases
    (λ h : r₁.1 ⊆ r₂.1, by rw real.if_pos h) (λ h, by rw real.if_neg h; exact (real.le_total r₁ r₂).resolve_left h),
  inf_le_right := λ r₁ r₂, show (real.ite (r₁.1 ⊆ r₂.1) r₁ r₂).1 ⊆ r₂.1, from classical.by_cases
    (λ h : r₁.1 ⊆ r₂.1, by rwa real.if_pos h) (λ h, by rw real.if_neg h),
  le_inf := λ r r₁ r₂ hr1 hr2, show r.1 ⊆ (real.ite (r₁.1 ⊆ r₂.1) r₁ r₂).1, from classical.by_cases
    (λ h : r₁.1 ⊆ r₂.1, by rwa real.if_pos h) (λ h, by rwa real.if_neg h),
  Sup := real.Sup,
  le_cSup := λ S r hS hrS, show r.1 ⊆ set.dite _ _ _,
    by rw set.dif_pos; [exact subset_bUnion_of_mem hrS, exact ⟨⟨r, hrS⟩, hS⟩],
  cSup_le := λ S r HS hr, show set.dite _ _ _ ⊆ r.1,
    by rw set.dif_pos; [exact bUnion_subset hr, exact ⟨exists_mem_of_ne_empty HS, r, hr⟩],
  cInf_le := λ S r HS hrS, show set.dite _ _ _ ⊆ _,
    by rw set.dif_pos; [exact bUnion_subset (λ m hm, hm r hrS), exact ⟨HS, r, λ m hm, hm r hrS⟩],
  le_cInf := λ S r HS hr, let ⟨s, hs⟩ := exists_mem_of_ne_empty HS in show r.1 ⊆ set.dite _ _ _,
    by rw set.dif_pos; [exact subset_bUnion_of_mem hr, exact ⟨⟨r, hr⟩, s, λ m hm, hm s hs⟩],
  Inf := real.Inf,
  .. partial_order.lift _ $ λ _ _, real.ext}

theorem of_rat_lt_iff (q : ℚ) (r : ℝ) : of_rat q < r ↔ q ∈ r :=
⟨λ hqr, let ⟨p, hpr, hpq⟩ := not_subset.1 hqr.2 in mem_of_mem_of_le _ p hpr _ (le_of_not_lt hpq),
λ hqr, ⟨λ p hpq, mem_of_mem_of_le _ q hqr _ (le_of_lt hpq),
λ hrq, lt_irrefl q $ hrq hqr⟩⟩

theorem le_of_rat_iff (q : ℚ) (r : ℝ) : r ≤ of_rat q ↔ q ∉ r :=
⟨λ hrq, mt (of_rat_lt_iff _ _).2 (not_lt_of_le hrq), λ hqr, le_of_not_lt $ mt (of_rat_lt_iff _ _).1 hqr⟩

def of_rat_embed : ((≤) : ℚ → ℚ → Prop) ≼o ((≤) : ℝ → ℝ → Prop) :=
{ to_fun := of_rat,
  inj := λ q₁ q₂ hq, le_antisymm
    (le_of_not_lt $ λ hq21 : q₂ ∈ of_rat q₁, lt_irrefl q₂ $ by rwa hq at hq21)
    (le_of_not_lt $ λ hq12 : q₁ ∈ of_rat q₂, lt_irrefl q₁ $ by rwa ← hq at hq12),
  ord := λ q₁ q₂, ⟨λ hq12, le_of_not_lt $ λ hq21, not_lt_of_le hq12 ((of_rat_lt_iff _ _).1 hq21),
    λ hq21, le_of_not_lt $ λ hq12, not_lt_of_le hq21 ((of_rat_lt_iff _ _).2 hq12)⟩ }

theorem of_rat_le_of_rat (q₁ q₂) : of_rat q₁ ≤ of_rat q₂ ↔ q₁ ≤ q₂ :=
(@order_embedding.ord _ _ _ _ of_rat_embed q₁ q₂).symm

theorem of_rat_lt_of_rat (q₁ q₂) : of_rat q₁ < of_rat q₂ ↔ q₁ < q₂ :=
of_rat_lt_iff _ _

theorem exists_rat_btwn_add (q : ℚ) (r) (hq : 0 < q) : ∃ m, of_rat m < r ∧ r < of_rat (m + q) :=
suffices ∀ (q:ℚ) (r:ℝ) (hq:q>0), ∃ m, m ∈ r ∧ m+q ∉ r,
from let ⟨m, hmr, hmqr⟩ := this (q/2) r (half_pos hq) in ⟨m, (of_rat_lt_iff _ _).2 hmr,
  lt_of_le_of_lt ((le_of_rat_iff _ _).2 hmqr) ((of_rat_lt_of_rat _ _).2 (add_lt_add_left (half_lt_self hq) _))⟩,
λ q r hq, let ⟨lo, hlo⟩ := r.2, ⟨hi, hhi⟩ := r.3 in
have ∀ n : ℕ, ∃ m, m ∈ r ∧ m + (hi - lo) / 2^n ∉ r,
  from λ n, nat.rec_on n ⟨lo, hlo, by rwa [pow_zero, div_one, add_sub_cancel'_right]⟩ $ λ n ⟨m, hmr, hmnr⟩,
  classical.by_cases (assume h : m + (hi - lo) / 2 ^ n.succ ∈ r,
    ⟨_, h, by rwa [add_assoc, pow_succ', ← div_div_eq_div_mul, add_halves]⟩)
  (λ h, ⟨m, hmr, h⟩),
let ⟨n, hn⟩ := pow_unbounded_of_gt_one ((hi - lo) / q) two_gt_one,
  ⟨m, hmr, hmnr⟩ := this n in
⟨m, hmr, mt (λ hmqr, r.4 _ hmqr (m + (hi-lo)/2^n) $ add_le_add_left
  (mul_div_cancel' q (ne_of_gt (pow_pos two_pos n)) ▸
    div_mul_div_cancel (hi-lo) (ne_of_gt (pow_pos two_pos n)) (ne_of_gt hq) ▸
    mul_le_mul_of_nonneg_right (le_of_lt hn) (div_nonneg (le_of_lt hq) (pow_pos two_pos n)))
  m) hmnr⟩

protected theorem exists_rat_btwn {r₁ r₂ : ℝ} (H : r₁ < r₂) :
  ∃ q, r₁ < of_rat q ∧ of_rat q < r₂ :=
let ⟨q, hq2, hq1⟩ := not_subset.1 H.2, ⟨q', hq2', hqq'⟩ := r₂.5 q hq2 in
⟨q', lt_of_le_of_lt ((le_of_rat_iff _ _).2 hq1) ((of_rat_lt_of_rat _ _).2 hqq'), (of_rat_lt_iff _ _).2 hq2'⟩

protected def add (r₁ r₂ : ℝ) : ℝ :=
{ carrier := (λ p:ℚ×ℚ, p.1+p.2) '' r₁.1.prod r₂.1,
  exists_carrier := let ⟨q₁, hq₁⟩ := r₁.2, ⟨q₂, hq₂⟩ := r₂.2 in
    ⟨q₁+q₂, @mem_image_of_mem _ _ (λ p:ℚ×ℚ, p.1+p.2) (q₁, q₂) (r₁.1.prod r₂.1) ⟨hq₁, hq₂⟩⟩,
  exists_not_carrier := let ⟨q₁, hq₁⟩ := r₁.3, ⟨q₂, hq₂⟩ := r₂.3 in
    ⟨q₁+q₂, λ ⟨⟨p₁,p₂⟩,⟨⟨hp₁,hp₂⟩,hp⟩⟩, absurd hp $ ne_of_lt $
      add_lt_add (lt_of_mem_of_not_mem r₁ hp₁ hq₁) (lt_of_mem_of_not_mem r₂ hp₂ hq₂)⟩,
  mem_of_mem_of_le := λ q ⟨⟨p₁,p₂⟩,⟨⟨hp₁,hp₂⟩,hp⟩⟩ p hpq,
    ⟨(p₁,p₂-(q-p)), ⟨hp₁, r₂.4 p₂ hp₂ _ (sub_le_self _ $ sub_nonneg_of_le hpq)⟩,
      by simp only [add_sub, (show p₁ + p₂ = q, from hp), sub_sub_cancel]⟩,
  exists_lt_of_mem := λ q ⟨⟨p₁,p₂⟩,⟨⟨hp₁,hp₂⟩,hp⟩⟩,
    let ⟨M₁,HM₁,hpM₁⟩ := r₁.5 p₁ hp₁, ⟨M₂,HM₂,hpM₂⟩ := r₂.5 p₂ hp₂ in
    ⟨M₁+M₂, @mem_image_of_mem _ _ (λ p:ℚ×ℚ, p.1+p.2) (M₁, M₂) (r₁.1.prod r₂.1) ⟨HM₁, HM₂⟩,
      hp ▸ add_lt_add hpM₁ hpM₂⟩ }

theorem rat_dense {r₁ r₂ : ℝ} (h : ∀ q, of_rat q < r₁ ↔ of_rat q < r₂) : r₁ = r₂ :=
ext $ set.ext $ λ q, (of_rat_lt_iff _ _).symm.trans $ (h q).trans $ of_rat_lt_iff _ _

theorem of_rat_lt_add_iff (q) (r₁ r₂) : of_rat q < real.add r₁ r₂ ↔
  ∃ q₁ ∈ r₁, ∃ q₂ ∈ r₂, q₁ + q₂ = q :=
(of_rat_lt_iff _ _).trans ⟨λ ⟨⟨x,y⟩,⟨hx,hy⟩,hxy⟩, ⟨x,hx,y,hy,hxy⟩, λ ⟨x,hx,y,hy,hxy⟩, ⟨⟨x,y⟩,⟨hx,hy⟩,hxy⟩⟩

protected def neg (r : ℝ) : ℝ :=
{ carrier := { q | r < of_rat (-q) },
  exists_carrier := let ⟨q, hqr⟩ := r.3 in ⟨-(q+1), show r < of_rat (- -(q+1)),
    from lt_of_not_ge $ mt (by rw _root_.neg_neg; exact λ hq1r,
      (of_rat_lt_iff _ _).1 (lt_of_lt_of_le ((of_rat_lt_of_rat _ _).2 (lt_add_one q)) hq1r)) hqr⟩,
  exists_not_carrier := let ⟨q, hqr⟩ := r.2 in ⟨-q,
    show ¬ r < of_rat (- -q), from (neg_neg q).symm ▸ not_lt_of_lt ((of_rat_lt_iff _ _).2 hqr)⟩,
  mem_of_mem_of_le := λ q hrq p hpq, lt_of_lt_of_le hrq $ (of_rat_le_of_rat _ _).2 (neg_le_neg hpq),
  exists_lt_of_mem := λ q hrq, let ⟨x, hrx, hxq⟩ := real.exists_rat_btwn hrq in
    ⟨-x, show r < of_rat (- -x), from (neg_neg x).symm ▸ hrx, lt_neg_of_lt_neg $ (of_rat_lt_of_rat _ _).1 hxq⟩ }

instance : add_comm_group ℝ :=
{ add := real.add,
  zero := of_rat 0,
  neg := real.neg,
  add_assoc := λ _ _ _, ext $ le_antisymm
    (image_subset_iff.2 $ λ ⟨xy,z⟩ ⟨⟨⟨x,y⟩,⟨hx,hy⟩,hxy⟩,hz⟩, ⟨(x,y+z),⟨hx,⟨(y,z),⟨hy,hz⟩,rfl⟩⟩,
      by simp only at hxy; exact hxy ▸ (add_assoc _ _ _).symm⟩)
    (image_subset_iff.2 $ λ ⟨x,yz⟩ ⟨hx,⟨⟨y,z⟩,⟨hy,hz⟩,hyz⟩⟩, ⟨(x+y,z),⟨⟨(x,y),⟨hx,hy⟩,rfl⟩,hz⟩,
      by simp only at hyz; exact hyz ▸ add_assoc _ _ _⟩),
  zero_add := λ r, rat_dense $ λ q, (of_rat_lt_add_iff _ _ _).trans
    ⟨λ ⟨q₁,hq₁,q₂,hq₂,hq⟩, hq ▸ (of_rat_lt_iff _ _).2 (r.4 q₂ hq₂ _ $ add_le_of_nonpos_of_le (le_of_lt hq₁) (le_refl _)),
    λ hqr, let ⟨M, HMr, hqM⟩ := r.5 q ((of_rat_lt_iff _ _).1 hqr) in ⟨q-M, sub_neg_of_lt hqM, M, HMr, sub_add_cancel _ _⟩⟩,
  add_zero := λ r, rat_dense $ λ q, (of_rat_lt_add_iff _ _ _).trans
    ⟨λ ⟨q₁,hq₁,q₂,hq₂,hq⟩, hq ▸ (of_rat_lt_iff _ _).2 (r.4 q₁ hq₁ _ $ add_le_of_le_of_nonpos (le_refl _) (le_of_lt hq₂)),
    λ hqr, let ⟨M, HMr, hqM⟩ := r.5 q ((of_rat_lt_iff _ _).1 hqr) in ⟨M, HMr, q-M, sub_neg_of_lt hqM, add_sub_cancel'_right _ _⟩⟩,
  add_left_neg := λ r, rat_dense $ λ q, (of_rat_lt_add_iff _ _ _).trans
    ⟨λ ⟨q₁,hq₁,q₂,hq₂,hq⟩, hq ▸ (of_rat_lt_iff _ _).2 (show q₁ + q₂ < 0,
      from add_comm q₂ q₁ ▸ (sub_neg_eq_add q₂ q₁) ▸ sub_neg_of_lt ((of_rat_lt_of_rat _ _).1 $
        lt_trans ((of_rat_lt_iff _ _).2 hq₂) hq₁)),
    λ hq, let ⟨m, hmr, hrmq⟩ := exists_rat_btwn_add (-q) r (neg_pos.2 ((of_rat_lt_of_rat _ _).1 hq)) in
    ⟨-(m-q), show r < of_rat (- -(m-q)), from (neg_neg (m-q)).symm ▸ hrmq,
    m, ((of_rat_lt_iff _ _).1 hmr), by rw [neg_sub, sub_add_cancel]⟩⟩,
  add_comm := λ _ _, ext $ le_antisymm
    (image_subset_iff.2 $ λ ⟨x,y⟩ ⟨hx,hy⟩, ⟨(y,x), ⟨hy,hx⟩, add_comm y x⟩)
    (image_subset_iff.2 $ λ ⟨x,y⟩ ⟨hx,hy⟩, ⟨(y,x), ⟨hy,hx⟩, add_comm y x⟩),
  .. real.conditionally_complete_linear_order }

def sign (r : ℝ) : ℝ :=
real.ite (r < of_rat 0) (of_rat (-1))
  (real.ite (of_rat 0 < r) (of_rat 1) (of_rat 0))

protected def abs (r : ℝ) : ℝ :=
r ⊔ -r

end real


open System

namespace Day2

def input : FilePath := "/home/fred/lean/aoc2022/input_02"

/-
PART 1:
The Elves begin to set up camp on the beach. To decide whose tent gets to be closest to the snack storage, a giant Rock Paper Scissors tournament is already in progress.

Rock Paper Scissors is a game between two players. Each game contains many rounds; in each round, the players each simultaneously choose one of Rock, Paper, or Scissors using a hand shape. Then, a winner for that round is selected: Rock defeats Scissors, Scissors defeats Paper, and Paper defeats Rock. If both players choose the same shape, the round instead ends in a draw.

Appreciative of your help yesterday, one Elf gives you an encrypted strategy guide (your puzzle input) that they say will be sure to help you win. "The first column is what your opponent is going to play: A for Rock, B for Paper, and C for Scissors. The second column--" Suddenly, the Elf is called away to help with someone's tent.

The second column, you reason, must be what you should play in response: X for Rock, Y for Paper, and Z for Scissors. Winning every time would be suspicious, so the responses must have been carefully chosen.

The winner of the whole tournament is the player with the highest score. Your total score is the sum of your scores for each round. The score for a single round is the score for the shape you selected (1 for Rock, 2 for Paper, and 3 for Scissors) plus the score for the outcome of the round (0 if you lost, 3 if the round was a draw, and 6 if you won).

Since you can't be sure if the Elf is trying to help you or trick you, you should calculate the score you would get if you were to follow the strategy guide.

For example, suppose you were given the following strategy guide:

A Y
B X
C Z

This strategy guide predicts and recommends the following:

    In the first round, your opponent will choose Rock (A), and you should choose Paper (Y). This ends in a win for you with a score of 8 (2 because you chose Paper + 6 because you won).
    In the second round, your opponent will choose Paper (B), and you should choose Rock (X). This ends in a loss for you with a score of 1 (1 + 0).
    The third round is a draw with both players choosing Scissors, giving you a score of 3 + 3 = 6.

In this example, if you were to follow the strategy guide, you would get a total score of 15 (8 + 1 + 6).

What would your total score be if everything goes exactly according to your strategy guide?
-/

/-- 0: rock, 1: paper, 2: scissors -/
def rps_translate : String → Option (Fin 3 × Fin 3)
| "A X" => some (0, 0)
| "A Y" => some (0, 1)
| "A Z" => some (0, 2)
| "B X" => some (1, 0)
| "B Y" => some (1, 1)
| "B Z" => some (1, 2)
| "C X" => some (2, 0)
| "C Y" => some (2, 1)
| "C Z" => some (2, 2)
| _     => none

/-- 0: Elf wins, 1: draw, 2: I win -/
def rps_outcome : Fin 3 × Fin 3 → Fin 3
| (0, 0) => 1
| (0, 1) => 2
| (0, 2) => 0
| (1, 0) => 0
| (1, 1) => 1
| (1, 2) => 2
| (2, 0) => 2
| (2, 1) => 0
| (2, 2) => 1

def rps_points (x : Option (Fin 3 × Fin 3)) : Nat :=
  match x with
  | some (elf, me) => me + 1 + 3 * (rps_outcome (elf, me))
  | none => 0

def first_part : IO Nat := do
  let rawdata ← IO.FS.lines input
  let pointslist := Array.map (rps_points ∘ rps_translate) rawdata
  return Array.foldl (· + ·) 0 pointslist

/-
PART 2:
The Elf finishes helping with the tent and sneaks back over to you. "Anyway, the second column says how the round needs to end: X means you need to lose, Y means you need to end the round in a draw, and Z means you need to win. Good luck!"

The total score is still calculated in the same way, but now you need to figure out what shape to choose so the round ends as indicated. The example above now goes like this:

    In the first round, your opponent will choose Rock (A), and you need the round to end in a draw (Y), so you also choose Rock. This gives you a score of 1 + 3 = 4.
    In the second round, your opponent will choose Paper (B), and you choose Rock so you lose (X) with a score of 1 + 0 = 1.
    In the third round, you will defeat your opponent's Scissors with Rock for a score of 1 + 6 = 7.

Now that you're correctly decrypting the ultra top secret strategy guide, you would get a total score of 12.

Following the Elf's instructions for the second column, what would your total score be if everything goes exactly according to your strategy guide?
-/

def rps_translate₂ : String → Option (Fin 3 × Fin 3)
| "A X" => some (0, 2)
| "A Y" => some (0, 0)
| "A Z" => some (0, 1)
| "B X" => some (1, 0)
| "B Y" => some (1, 1)
| "B Z" => some (1, 2)
| "C X" => some (2, 1)
| "C Y" => some (2, 2)
| "C Z" => some (2, 0)
| _     => none

def second_part : IO Nat := do
  let rawdata ← IO.FS.lines input
  let pointslist := Array.map (rps_points ∘ rps_translate₂) rawdata
  return Array.foldl (· + ·) 0 pointslist

end Day2


/-
Copyright (c) 2020 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/
import category_theory.limits.shapes.terminal
import category_theory.limits.shapes.binary_products
import category_theory.subobject.basic

/-!
# Subterminal objects

Subterminal objects are the objects which can be thought of as subobjects of the terminal object.
In fact, the definition can be constructed to not require a terminal object, by defining `A` to be
subterminal iff for any `Z`, there is at most one morphism `Z ⟶ A`.
An alternate definition is that the diagonal morphism `A ⟶ A ⨯ A` is an isomorphism.
In this file we define subterminal objects and show the equivalence of these three definitions.

We also construct the subcategory of subterminal objects.

## TODO

* Define exponential ideals, and show this subcategory is an exponential ideal.
* Use the above to show that in a locally cartesian closed category, every subobject lattice
  is cartesian closed (equivalently, a Heyting algebra).

-/
universes v₁ v₂ u₁ u₂

noncomputable theory

namespace category_theory

open limits category

variables {C : Type u₁} [category.{v₁} C] {A : C}

/-- An object `A` is subterminal iff for any `Z`, there is at most one morphism `Z ⟶ A`. -/
def is_subterminal (A : C) : Prop := ∀ ⦃Z : C⦄ (f g : Z ⟶ A), f = g

lemma is_subterminal.def : is_subterminal A ↔ ∀ ⦃Z : C⦄ (f g : Z ⟶ A), f = g := iff.rfl

/--
If `A` is subterminal, the unique morphism from it to a terminal object is a monomorphism.
The converse of `is_subterminal_of_mono_is_terminal_from`.
-/
lemma is_subterminal.mono_is_terminal_from (hA : is_subterminal A) {T : C} (hT : is_terminal T) :
  mono (hT.from A) :=
{ right_cancellation := λ Z g h _, hA _ _ }

/--
If `A` is subterminal, the unique morphism from it to the terminal object is a monomorphism.
The converse of `is_subterminal_of_mono_terminal_from`.
-/
lemma is_subterminal.mono_terminal_from [has_terminal C] (hA : is_subterminal A) :
  mono (terminal.from A) :=
hA.mono_is_terminal_from terminal_is_terminal

/--
If the unique morphism from `A` to a terminal object is a monomorphism, `A` is subterminal.
The converse of `is_subterminal.mono_is_terminal_from`.
-/
lemma is_subterminal_of_mono_is_terminal_from {T : C} (hT : is_terminal T) [mono (hT.from A)] :
  is_subterminal A :=
λ Z f g, by { rw ← cancel_mono (hT.from A), apply hT.hom_ext }

/--
If the unique morphism from `A` to the terminal object is a monomorphism, `A` is subterminal.
The converse of `is_subterminal.mono_terminal_from`.
-/
lemma is_subterminal_of_mono_terminal_from [has_terminal C] [mono (terminal.from A)] :
  is_subterminal A :=
λ Z f g, by { rw ← cancel_mono (terminal.from A), apply subsingleton.elim }

lemma is_subterminal_of_is_terminal {T : C} (hT : is_terminal T) : is_subterminal T :=
λ Z f g, hT.hom_ext _ _

lemma is_subterminal_of_terminal [has_terminal C] : is_subterminal (⊤_ C) :=
λ Z f g, subsingleton.elim _ _

/--
If `A` is subterminal, its diagonal morphism is an isomorphism.
The converse of `is_subterminal_of_is_iso_diag`.
-/
lemma is_subterminal.is_iso_diag (hA : is_subterminal A) [has_binary_product A A] :
  is_iso (diag A) :=
⟨⟨limits.prod.fst, ⟨by simp, by { rw is_subterminal.def at hA, tidy }⟩⟩⟩

/--
If the diagonal morphism of `A` is an isomorphism, then it is subterminal.
The converse of `is_subterminal.is_iso_diag`.
-/
lemma is_subterminal_of_is_iso_diag [has_binary_product A A] [is_iso (diag A)] :
  is_subterminal A :=
λ Z f g,
begin
  have : (limits.prod.fst : A ⨯ A ⟶ _) = limits.prod.snd,
  { simp [←cancel_epi (diag A)] },
  rw [←prod.lift_fst f g, this, prod.lift_snd],
end

/-- If `A` is subterminal, it is isomorphic to `A ⨯ A`. -/
@[simps]
def is_subterminal.iso_diag (hA : is_subterminal A) [has_binary_product A A] :
  A ⨯ A ≅ A :=
begin
  letI := is_subterminal.is_iso_diag hA,
  apply (as_iso (diag A)).symm,
end

variables (C)
/--
The (full sub)category of subterminal objects.
TODO: If `C` is the category of sheaves on a topological space `X`, this category is equivalent
to the lattice of open subsets of `X`. More generally, if `C` is a topos, this is the lattice of
"external truth values".
-/
@[derive category]
def subterminals (C : Type u₁) [category.{v₁} C] :=
{A : C // is_subterminal A}

instance [has_terminal C] : inhabited (subterminals C) :=
⟨⟨⊤_ C, is_subterminal_of_terminal⟩⟩

/-- The inclusion of the subterminal objects into the original category. -/
@[derive [full, faithful], simps]
def subterminal_inclusion : subterminals C ⥤ C := full_subcategory_inclusion _

instance subterminals_thin (X Y : subterminals C) : subsingleton (X ⟶ Y) :=
⟨λ f g, Y.2 f g⟩

/--
The category of subterminal objects is equivalent to the category of monomorphisms to the terminal
object (which is in turn equivalent to the subobjects of the terminal object).
-/
@[simps]
def subterminals_equiv_mono_over_terminal [has_terminal C] :
  subterminals C ≌ mono_over (⊤_ C) :=
{ functor :=
  { obj := λ X, ⟨over.mk (terminal.from X.1), X.2.mono_terminal_from⟩,
    map := λ X Y f, mono_over.hom_mk f (by ext1 ⟨⟩) },
  inverse :=
  { obj := λ X, ⟨X.val.left, λ Z f g, by { rw ← cancel_mono X.arrow, apply subsingleton.elim }⟩,
    map := λ X Y f, f.1 },
  unit_iso :=
  { hom := { app := λ X, 𝟙 _ },
    inv := { app := λ X, 𝟙 _ } },
  counit_iso :=
  { hom := { app := λ X, over.hom_mk (𝟙 _) },
    inv := { app := λ X, over.hom_mk (𝟙 _) } } }

@[simp]
lemma subterminals_to_mono_over_terminal_comp_forget [has_terminal C] :
  (subterminals_equiv_mono_over_terminal C).functor ⋙ mono_over.forget _ ⋙ over.forget _ =
    subterminal_inclusion C :=
rfl

@[simp]
lemma mono_over_terminal_to_subterminals_comp [has_terminal C] :
  (subterminals_equiv_mono_over_terminal C).inverse ⋙ subterminal_inclusion C =
    mono_over.forget _ ⋙ over.forget _ :=
rfl

end category_theory


--

structure S  :=
(g {α} : α → α)

def f (h : Nat → ({α : Type} → α → α) × Bool) : Nat :=
(h 0).1 1

def tst : Nat :=
f fun n => (fun x => x, true)

theorem ex : id (Nat → Nat) :=
by {
  intro;
  assumption
}

def g (i j k : Nat) (a : Array Nat) (h₁ : i < k) (h₂ : k < j) (h₃ : j < a.size) : Nat :=
  let vj := a.get ⟨j, h₃⟩;
  let vi := a.get ⟨i, Nat.lt_trans h₁ (Nat.lt_trans h₂ h₃)⟩;
  vi + vj

set_option pp.all true in
#print g

#check g.proof_1

theorem ex1 {p q r s : Prop} : p ∧ q ∧ r ∧ s → r ∧ s ∧ q ∧ p :=
  fun ⟨hp, hq, hr, hs⟩ => ⟨hr, hs, hq, hp⟩

theorem ex2 {p q r s : Prop} : p ∧ q ∧ r ∧ s → r ∧ s ∧ q ∧ p := by
  intro ⟨hp, hq, hr, hs⟩
  exact ⟨hr, hs, hq, hp⟩


/-
Copyright (c) 2020 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel, Yury Kudryashov
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.calculus.formal_multilinear_series
import Mathlib.analysis.specific_limits
import Mathlib.PostPort

universes u_1 u_2 u_3 l

namespace Mathlib

/-!
# Analytic functions

A function is analytic in one dimension around `0` if it can be written as a converging power series
`Σ pₙ zⁿ`. This definition can be extended to any dimension (even in infinite dimension) by
requiring that `pₙ` is a continuous `n`-multilinear map. In general, `pₙ` is not unique (in two
dimensions, taking `p₂ (x, y) (x', y') = x y'` or `y x'` gives the same map when applied to a
vector `(x, y) (x, y)`). A way to guarantee uniqueness is to take a symmetric `pₙ`, but this is not
always possible in nonzero characteristic (in characteristic 2, the previous example has no
symmetric representative). Therefore, we do not insist on symmetry or uniqueness in the definition,
and we only require the existence of a converging series.

The general framework is important to say that the exponential map on bounded operators on a Banach
space is analytic, as well as the inverse on invertible operators.

## Main definitions

Let `p` be a formal multilinear series from `E` to `F`, i.e., `p n` is a multilinear map on `E^n`
for `n : ℕ`.

* `p.radius`: the largest `r : ennreal` such that `∥p n∥ * r^n` grows subexponentially, defined as
  a liminf.
* `p.le_radius_of_bound`, `p.le_radius_of_bound_nnreal`, `p.le_radius_of_is_O`: if `∥p n∥ * r ^ n`
  is bounded above, then `r ≤ p.radius`;
* `p.is_o_of_lt_radius`, `p.norm_mul_pow_le_mul_pow_of_lt_radius`, `p.is_o_one_of_lt_radius`,
  `p.norm_mul_pow_le_of_lt_radius`, `p.nnnorm_mul_pow_le_of_lt_radius`: if `r < p.radius`, then
  `∥p n∥ * r ^ n` tends to zero exponentially;
* `p.lt_radius_of_is_O`: if `r ≠ 0` and `∥p n∥ * r ^ n = O(a ^ n)` for some `-1 < a < 1`, then
  `r < p.radius`;
* `p.partial_sum n x`: the sum `∑_{i = 0}^{n-1} pᵢ xⁱ`.
* `p.sum x`: the sum `∑'_{i = 0}^{∞} pᵢ xⁱ`.

Additionally, let `f` be a function from `E` to `F`.

* `has_fpower_series_on_ball f p x r`: on the ball of center `x` with radius `r`,
  `f (x + y) = ∑'_n pₙ yⁿ`.
* `has_fpower_series_at f p x`: on some ball of center `x` with positive radius, holds
  `has_fpower_series_on_ball f p x r`.
* `analytic_at 𝕜 f x`: there exists a power series `p` such that holds
  `has_fpower_series_at f p x`.

We develop the basic properties of these notions, notably:
* If a function admits a power series, it is continuous (see
  `has_fpower_series_on_ball.continuous_on` and `has_fpower_series_at.continuous_at` and
  `analytic_at.continuous_at`).
* In a complete space, the sum of a formal power series with positive radius is well defined on the
  disk of convergence, see `formal_multilinear_series.has_fpower_series_on_ball`.
* If a function admits a power series in a ball, then it is analytic at any point `y` of this ball,
  and the power series there can be expressed in terms of the initial power series `p` as
  `p.change_origin y`. See `has_fpower_series_on_ball.change_origin`. It follows in particular that
  the set of points at which a given function is analytic is open, see `is_open_analytic_at`.

## Implementation details

We only introduce the radius of convergence of a power series, as `p.radius`.
For a power series in finitely many dimensions, there is a finer (directional, coordinate-dependent)
notion, describing the polydisk of convergence. This notion is more specific, and not necessary to
build the general theory. We do not define it here.
-/

/-! ### The radius of a formal multilinear series -/

namespace formal_multilinear_series

/-- The radius of a formal multilinear series is the largest `r` such that the sum `Σ pₙ yⁿ`
converges for all `∥y∥ < r`. -/
def radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) : ennreal :=
  supr
    fun (r : nnreal) => supr fun (C : ℝ) => supr fun (hr : ∀ (n : ℕ), norm (p n) * ↑r ^ n ≤ C) => ↑r

/-- If `∥pₙ∥ rⁿ` is bounded in `n`, then the radius of `p` is at least `r`. -/
theorem le_radius_of_bound {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) (C : ℝ) {r : nnreal}
    (h : ∀ (n : ℕ), norm (p n) * ↑r ^ n ≤ C) : ↑r ≤ radius p :=
  le_supr_of_le r
    (le_supr_of_le C (le_supr (fun (_x : ∀ (n : ℕ), norm (p n) * ↑r ^ n ≤ C) => ↑r) h))

/-- If `∥pₙ∥ rⁿ` is bounded in `n`, then the radius of `p` is at least `r`. -/
theorem le_radius_of_bound_nnreal {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) (C : nnreal) {r : nnreal}
    (h : ∀ (n : ℕ), nnnorm (p n) * r ^ n ≤ C) : ↑r ≤ radius p :=
  sorry

/-- If `∥pₙ∥ rⁿ = O(1)`, as `n → ∞`, then the radius of `p` is at least `r`. -/
theorem le_radius_of_is_O {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {r : nnreal}
    (h : asymptotics.is_O (fun (n : ℕ) => norm (p n) * ↑r ^ n) (fun (n : ℕ) => 1) filter.at_top) :
    ↑r ≤ radius p :=
  exists.elim (iff.mp asymptotics.is_O_one_nat_at_top_iff h)
    fun (C : ℝ) (hC : ∀ (n : ℕ), norm (norm (p n) * ↑r ^ n) ≤ C) =>
      le_radius_of_bound p C
        fun (n : ℕ) => has_le.le.trans (le_abs_self (norm (p n) * ↑r ^ n)) (hC n)

theorem radius_eq_top_of_forall_nnreal_is_O {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] (p : formal_multilinear_series 𝕜 E F)
    (h :
      ∀ (r : nnreal),
        asymptotics.is_O (fun (n : ℕ) => norm (p n) * ↑r ^ n) (fun (n : ℕ) => 1) filter.at_top) :
    radius p = ⊤ :=
  ennreal.eq_top_of_forall_nnreal_le fun (r : nnreal) => le_radius_of_is_O p (h r)

theorem radius_eq_top_of_eventually_eq_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] (p : formal_multilinear_series 𝕜 E F)
    (h : filter.eventually (fun (n : ℕ) => p n = 0) filter.at_top) : radius p = ⊤ :=
  sorry

theorem radius_eq_top_of_forall_image_add_eq_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] (p : formal_multilinear_series 𝕜 E F) (n : ℕ)
    (hn : ∀ (m : ℕ), p (m + n) = 0) : radius p = ⊤ :=
  radius_eq_top_of_eventually_eq_zero p
    (iff.mpr filter.mem_at_top_sets
      (Exists.intro n fun (k : ℕ) (hk : k ≥ n) => nat.sub_add_cancel hk ▸ hn (k - n)))

/-- For `r` strictly smaller than the radius of `p`, then `∥pₙ∥ rⁿ` tends to zero exponentially:
for some `0 < a < 1`, `∥p n∥ rⁿ = o(aⁿ)`. -/
theorem is_o_of_lt_radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {r : nnreal} (h : ↑r < radius p) :
    ∃ (a : ℝ),
        ∃ (H : a ∈ set.Ioo 0 1),
          asymptotics.is_o (fun (n : ℕ) => norm (p n) * ↑r ^ n) (pow a) filter.at_top :=
  sorry

/-- For `r` strictly smaller than the radius of `p`, then `∥pₙ∥ rⁿ = o(1)`. -/
theorem is_o_one_of_lt_radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {r : nnreal} (h : ↑r < radius p) :
    asymptotics.is_o (fun (n : ℕ) => norm (p n) * ↑r ^ n) (fun (_x : ℕ) => 1) filter.at_top :=
  sorry

/-- For `r` strictly smaller than the radius of `p`, then `∥pₙ∥ rⁿ` tends to zero exponentially:
for some `0 < a < 1` and `C > 0`,  `∥p n∥ * r ^ n ≤ C * a ^ n`. -/
theorem norm_mul_pow_le_mul_pow_of_lt_radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] (p : formal_multilinear_series 𝕜 E F) {r : nnreal} (h : ↑r < radius p) :
    ∃ (a : ℝ),
        ∃ (H : a ∈ set.Ioo 0 1),
          ∃ (C : ℝ), ∃ (H : C > 0), ∀ (n : ℕ), norm (p n) * ↑r ^ n ≤ C * a ^ n :=
  sorry

/-- If `r ≠ 0` and `∥pₙ∥ rⁿ = O(aⁿ)` for some `-1 < a < 1`, then `r < p.radius`. -/
theorem lt_radius_of_is_O {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {r : nnreal} (h₀ : r ≠ 0) {a : ℝ}
    (ha : a ∈ set.Ioo (-1) 1)
    (hp : asymptotics.is_O (fun (n : ℕ) => norm (p n) * ↑r ^ n) (pow a) filter.at_top) :
    ↑r < radius p :=
  sorry

/-- For `r` strictly smaller than the radius of `p`, then `∥pₙ∥ rⁿ` is bounded. -/
theorem norm_mul_pow_le_of_lt_radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {r : nnreal} (h : ↑r < radius p) :
    ∃ (C : ℝ), ∃ (H : C > 0), ∀ (n : ℕ), norm (p n) * ↑r ^ n ≤ C :=
  sorry

/-- For `r` strictly smaller than the radius of `p`, then `∥pₙ∥ rⁿ` is bounded. -/
theorem norm_le_div_pow_of_pos_of_lt_radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] (p : formal_multilinear_series 𝕜 E F) {r : nnreal} (h0 : 0 < r)
    (h : ↑r < radius p) : ∃ (C : ℝ), ∃ (H : C > 0), ∀ (n : ℕ), norm (p n) ≤ C / ↑r ^ n :=
  sorry

/-- For `r` strictly smaller than the radius of `p`, then `∥pₙ∥ rⁿ` is bounded. -/
theorem nnnorm_mul_pow_le_of_lt_radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {r : nnreal} (h : ↑r < radius p) :
    ∃ (C : nnreal), ∃ (H : C > 0), ∀ (n : ℕ), nnnorm (p n) * r ^ n ≤ C :=
  sorry

/-- The radius of the sum of two formal series is at least the minimum of their two radii. -/
theorem min_radius_le_radius_add {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) (q : formal_multilinear_series 𝕜 E F) :
    min (radius p) (radius q) ≤ radius (p + q) :=
  sorry

@[simp] theorem radius_neg {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) : radius (-p) = radius p :=
  sorry

/-- Given a formal multilinear series `p` and a vector `x`, then `p.sum x` is the sum `Σ pₙ xⁿ`. A
priori, it only behaves well when `∥x∥ < p.radius`. -/
protected def sum {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) (x : E) : F :=
  tsum fun (n : ℕ) => coe_fn (p n) fun (i : fin n) => x

/-- Given a formal multilinear series `p` and a vector `x`, then `p.partial_sum n x` is the sum
`Σ pₖ xᵏ` for `k ∈ {0,..., n-1}`. -/
def partial_sum {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) (n : ℕ) (x : E) : F :=
  finset.sum (finset.range n) fun (k : ℕ) => coe_fn (p k) fun (i : fin k) => x

/-- The partial sums of a formal multilinear series are continuous. -/
theorem partial_sum_continuous {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) (n : ℕ) : continuous (partial_sum p n) :=
  sorry

end formal_multilinear_series

/-! ### Expanding a function as a power series -/

/-- Given a function `f : E → F` and a formal multilinear series `p`, we say that `f` has `p` as
a power series on the ball of radius `r > 0` around `x` if `f (x + y) = ∑' pₙ yⁿ` for all `∥y∥ < r`.
-/
structure has_fpower_series_on_ball {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (f : E → F) (p : formal_multilinear_series 𝕜 E F) (x : E) (r : ennreal)
    where
  r_le : r ≤ formal_multilinear_series.radius p
  r_pos : 0 < r
  has_sum :
    ∀ {y : E},
      y ∈ emetric.ball 0 r → has_sum (fun (n : ℕ) => coe_fn (p n) fun (i : fin n) => y) (f (x + y))

/-- Given a function `f : E → F` and a formal multilinear series `p`, we say that `f` has `p` as
a power series around `x` if `f (x + y) = ∑' pₙ yⁿ` for all `y` in a neighborhood of `0`. -/
def has_fpower_series_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] (f : E → F)
    (p : formal_multilinear_series 𝕜 E F) (x : E) :=
  ∃ (r : ennreal), has_fpower_series_on_ball f p x r

/-- Given a function `f : E → F`, we say that `f` is analytic at `x` if it admits a convergent power
series expansion around `x`. -/
def analytic_at (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] (f : E → F) (x : E) :=
  ∃ (p : formal_multilinear_series 𝕜 E F), has_fpower_series_at f p x

theorem has_fpower_series_on_ball.has_fpower_series_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f p x r) : has_fpower_series_at f p x :=
  Exists.intro r hf

theorem has_fpower_series_at.analytic_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} (hf : has_fpower_series_at f p x) :
    analytic_at 𝕜 f x :=
  Exists.intro p hf

theorem has_fpower_series_on_ball.analytic_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f p x r) : analytic_at 𝕜 f x :=
  has_fpower_series_at.analytic_at (has_fpower_series_on_ball.has_fpower_series_at hf)

theorem has_fpower_series_on_ball.has_sum_sub {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f p x r) {y : E} (hy : y ∈ emetric.ball x r) :
    has_sum (fun (n : ℕ) => coe_fn (p n) fun (i : fin n) => y - x) (f y) :=
  sorry

theorem has_fpower_series_on_ball.radius_pos {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f p x r) : 0 < formal_multilinear_series.radius p :=
  lt_of_lt_of_le (has_fpower_series_on_ball.r_pos hf) (has_fpower_series_on_ball.r_le hf)

theorem has_fpower_series_at.radius_pos {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} (hf : has_fpower_series_at f p x) :
    0 < formal_multilinear_series.radius p :=
  sorry

theorem has_fpower_series_on_ball.mono {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal} {r' : ennreal}
    (hf : has_fpower_series_on_ball f p x r) (r'_pos : 0 < r') (hr : r' ≤ r) :
    has_fpower_series_on_ball f p x r' :=
  has_fpower_series_on_ball.mk (le_trans hr (has_fpower_series_on_ball.r_le hf)) r'_pos
    fun (y : E) (hy : y ∈ emetric.ball 0 r') =>
      has_fpower_series_on_ball.has_sum hf (emetric.ball_subset_ball hr hy)

protected theorem has_fpower_series_at.eventually {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    (hf : has_fpower_series_at f p x) :
    filter.eventually (fun (r : ennreal) => has_fpower_series_on_ball f p x r)
        (nhds_within 0 (set.Ioi 0)) :=
  sorry

theorem has_fpower_series_on_ball.add {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {g : E → F} {pf : formal_multilinear_series 𝕜 E F}
    {pg : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f pf x r) (hg : has_fpower_series_on_ball g pg x r) :
    has_fpower_series_on_ball (f + g) (pf + pg) x r :=
  sorry

theorem has_fpower_series_at.add {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {g : E → F} {pf : formal_multilinear_series 𝕜 E F}
    {pg : formal_multilinear_series 𝕜 E F} {x : E} (hf : has_fpower_series_at f pf x)
    (hg : has_fpower_series_at g pg x) : has_fpower_series_at (f + g) (pf + pg) x :=
  sorry

theorem analytic_at.add {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] {f : E → F} {g : E → F}
    {x : E} (hf : analytic_at 𝕜 f x) (hg : analytic_at 𝕜 g x) : analytic_at 𝕜 (f + g) x :=
  sorry

theorem has_fpower_series_on_ball.neg {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {pf : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f pf x r) : has_fpower_series_on_ball (-f) (-pf) x r :=
  sorry

theorem has_fpower_series_at.neg {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {pf : formal_multilinear_series 𝕜 E F} {x : E} (hf : has_fpower_series_at f pf x) :
    has_fpower_series_at (-f) (-pf) x :=
  sorry

theorem analytic_at.neg {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] {f : E → F} {x : E}
    (hf : analytic_at 𝕜 f x) : analytic_at 𝕜 (-f) x :=
  sorry

theorem has_fpower_series_on_ball.sub {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {g : E → F} {pf : formal_multilinear_series 𝕜 E F}
    {pg : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f pf x r) (hg : has_fpower_series_on_ball g pg x r) :
    has_fpower_series_on_ball (f - g) (pf - pg) x r :=
  sorry

theorem has_fpower_series_at.sub {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {g : E → F} {pf : formal_multilinear_series 𝕜 E F}
    {pg : formal_multilinear_series 𝕜 E F} {x : E} (hf : has_fpower_series_at f pf x)
    (hg : has_fpower_series_at g pg x) : has_fpower_series_at (f - g) (pf - pg) x :=
  sorry

theorem analytic_at.sub {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F] {f : E → F} {g : E → F}
    {x : E} (hf : analytic_at 𝕜 f x) (hg : analytic_at 𝕜 g x) : analytic_at 𝕜 (f - g) x :=
  sorry

theorem has_fpower_series_on_ball.coeff_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {pf : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f pf x r) (v : fin 0 → E) : coe_fn (pf 0) v = f x :=
  sorry

theorem has_fpower_series_at.coeff_zero {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {pf : formal_multilinear_series 𝕜 E F} {x : E} (hf : has_fpower_series_at f pf x)
    (v : fin 0 → E) : coe_fn (pf 0) v = f x :=
  sorry

/-- If a function admits a power series expansion, then it is exponentially close to the partial
sums of this power series on strict subdisks of the disk of convergence.

This version provides an upper estimate that decreases both in `∥y∥` and `n`. See also
`has_fpower_series_on_ball.uniform_geometric_approx` for a weaker version. -/
theorem has_fpower_series_on_ball.uniform_geometric_approx' {𝕜 : Type u_1}
    [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3}
    [normed_group F] [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    {r : ennreal} {r' : nnreal} (hf : has_fpower_series_on_ball f p x r) (h : ↑r' < r) :
    ∃ (a : ℝ),
        ∃ (H : a ∈ set.Ioo 0 1),
          ∃ (C : ℝ),
            ∃ (H : C > 0),
              ∀ (y : E),
                y ∈ metric.ball 0 ↑r' →
                  ∀ (n : ℕ),
                    norm (f (x + y) - formal_multilinear_series.partial_sum p n y) ≤
                      C * (a * (norm y / ↑r')) ^ n :=
  sorry

/-- If a function admits a power series expansion, then it is exponentially close to the partial
sums of this power series on strict subdisks of the disk of convergence. -/
theorem has_fpower_series_on_ball.uniform_geometric_approx {𝕜 : Type u_1}
    [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3}
    [normed_group F] [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    {r : ennreal} {r' : nnreal} (hf : has_fpower_series_on_ball f p x r) (h : ↑r' < r) :
    ∃ (a : ℝ),
        ∃ (H : a ∈ set.Ioo 0 1),
          ∃ (C : ℝ),
            ∃ (H : C > 0),
              ∀ (y : E),
                y ∈ metric.ball 0 ↑r' →
                  ∀ (n : ℕ),
                    norm (f (x + y) - formal_multilinear_series.partial_sum p n y) ≤ C * a ^ n :=
  sorry

/-- Taylor formula for an analytic function, `is_O` version. -/
theorem has_fpower_series_at.is_O_sub_partial_sum_pow {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    (hf : has_fpower_series_at f p x) (n : ℕ) :
    asymptotics.is_O (fun (y : E) => f (x + y) - formal_multilinear_series.partial_sum p n y)
        (fun (y : E) => norm y ^ n) (nhds 0) :=
  sorry

-- hack to speed up simp when dealing with complicated types

/-- If `f` has formal power series `∑ n, pₙ` on a ball of radius `r`, then for `y, z` in any smaller
ball, the norm of the difference `f y - f z - p 1 (λ _, y - z)` is bounded above by
`C * (max ∥y - x∥ ∥z - x∥) * ∥y - z∥`. This lemma formulates this property using `is_O` and
`filter.principal` on `E × E`. -/
theorem has_fpower_series_on_ball.is_O_image_sub_image_sub_deriv_principal {𝕜 : Type u_1}
    [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3}
    [normed_group F] [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    {r : ennreal} {r' : ennreal} (hf : has_fpower_series_on_ball f p x r) (hr : r' < r) :
    asymptotics.is_O
        (fun (y : E × E) =>
          f (prod.fst y) - f (prod.snd y) -
            coe_fn (p 1) fun (_x : fin 1) => prod.fst y - prod.snd y)
        (fun (y : E × E) => norm (y - (x, x)) * norm (prod.fst y - prod.snd y))
        (filter.principal (emetric.ball (x, x) r')) :=
  sorry

/-- If `f` has formal power series `∑ n, pₙ` on a ball of radius `r`, then for `y, z` in any smaller
ball, the norm of the difference `f y - f z - p 1 (λ _, y - z)` is bounded above by
`C * (max ∥y - x∥ ∥z - x∥) * ∥y - z∥`. -/
theorem has_fpower_series_on_ball.image_sub_sub_deriv_le {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    {r' : ennreal} (hf : has_fpower_series_on_ball f p x r) (hr : r' < r) :
    ∃ (C : ℝ),
        ∀ (y z : E),
          y ∈ emetric.ball x r' →
            z ∈ emetric.ball x r' →
              norm (f y - f z - coe_fn (p 1) fun (_x : fin 1) => y - z) ≤
                C * max (norm (y - x)) (norm (z - x)) * norm (y - z) :=
  sorry

/-- If `f` has formal power series `∑ n, pₙ` at `x`, then
`f y - f z - p 1 (λ _, y - z) = O(∥(y, z) - (x, x)∥ * ∥y - z∥)` as `(y, z) → (x, x)`.
In particular, `f` is strictly differentiable at `x`. -/
theorem has_fpower_series_at.is_O_image_sub_norm_mul_norm_sub {𝕜 : Type u_1}
    [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3}
    [normed_group F] [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    (hf : has_fpower_series_at f p x) :
    asymptotics.is_O
        (fun (y : E × E) =>
          f (prod.fst y) - f (prod.snd y) -
            coe_fn (p 1) fun (_x : fin 1) => prod.fst y - prod.snd y)
        (fun (y : E × E) => norm (y - (x, x)) * norm (prod.fst y - prod.snd y)) (nhds (x, x)) :=
  sorry

/-- If a function admits a power series expansion at `x`, then it is the uniform limit of the
partial sums of this power series on strict subdisks of the disk of convergence, i.e., `f (x + y)`
is the uniform limit of `p.partial_sum n y` there. -/
theorem has_fpower_series_on_ball.tendsto_uniformly_on {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    {r' : nnreal} (hf : has_fpower_series_on_ball f p x r) (h : ↑r' < r) :
    tendsto_uniformly_on (fun (n : ℕ) (y : E) => formal_multilinear_series.partial_sum p n y)
        (fun (y : E) => f (x + y)) filter.at_top (metric.ball 0 ↑r') :=
  sorry

/-- If a function admits a power series expansion at `x`, then it is the locally uniform limit of
the partial sums of this power series on the disk of convergence, i.e., `f (x + y)`
is the locally uniform limit of `p.partial_sum n y` there. -/
theorem has_fpower_series_on_ball.tendsto_locally_uniformly_on {𝕜 : Type u_1}
    [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3}
    [normed_group F] [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    {r : ennreal} (hf : has_fpower_series_on_ball f p x r) :
    tendsto_locally_uniformly_on
        (fun (n : ℕ) (y : E) => formal_multilinear_series.partial_sum p n y)
        (fun (y : E) => f (x + y)) filter.at_top (emetric.ball 0 r) :=
  sorry

/-- If a function admits a power series expansion at `x`, then it is the uniform limit of the
partial sums of this power series on strict subdisks of the disk of convergence, i.e., `f y`
is the uniform limit of `p.partial_sum n (y - x)` there. -/
theorem has_fpower_series_on_ball.tendsto_uniformly_on' {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    {r' : nnreal} (hf : has_fpower_series_on_ball f p x r) (h : ↑r' < r) :
    tendsto_uniformly_on (fun (n : ℕ) (y : E) => formal_multilinear_series.partial_sum p n (y - x))
        f filter.at_top (metric.ball x ↑r') :=
  sorry

/-- If a function admits a power series expansion at `x`, then it is the locally uniform limit of
the  partial sums of this power series on the disk of convergence, i.e., `f y`
is the locally uniform limit of `p.partial_sum n (y - x)` there. -/
theorem has_fpower_series_on_ball.tendsto_locally_uniformly_on' {𝕜 : Type u_1}
    [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3}
    [normed_group F] [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    {r : ennreal} (hf : has_fpower_series_on_ball f p x r) :
    tendsto_locally_uniformly_on
        (fun (n : ℕ) (y : E) => formal_multilinear_series.partial_sum p n (y - x)) f filter.at_top
        (emetric.ball x r) :=
  sorry

/-- If a function admits a power series expansion on a disk, then it is continuous there. -/
theorem has_fpower_series_on_ball.continuous_on {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal}
    (hf : has_fpower_series_on_ball f p x r) : continuous_on f (emetric.ball x r) :=
  sorry

theorem has_fpower_series_at.continuous_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    (hf : has_fpower_series_at f p x) : continuous_at f x :=
  sorry

theorem analytic_at.continuous_at {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {x : E} (hf : analytic_at 𝕜 f x) : continuous_at f x :=
  sorry

/-- In a complete space, the sum of a converging power series `p` admits `p` as a power series.
This is not totally obvious as we need to check the convergence of the series. -/
theorem formal_multilinear_series.has_fpower_series_on_ball {𝕜 : Type u_1}
    [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3}
    [normed_group F] [normed_space 𝕜 F] [complete_space F] (p : formal_multilinear_series 𝕜 E F)
    (h : 0 < formal_multilinear_series.radius p) :
    has_fpower_series_on_ball (formal_multilinear_series.sum p) p 0
        (formal_multilinear_series.radius p) :=
  sorry

theorem has_fpower_series_on_ball.sum {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E} {r : ennreal} [complete_space F]
    (h : has_fpower_series_on_ball f p x r) {y : E} (hy : y ∈ emetric.ball 0 r) :
    f (x + y) = formal_multilinear_series.sum p y :=
  sorry

/-- The sum of a converging power series is continuous in its disk of convergence. -/
theorem formal_multilinear_series.continuous_on {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] {p : formal_multilinear_series 𝕜 E F} [complete_space F] :
    continuous_on (formal_multilinear_series.sum p)
        (emetric.ball 0 (formal_multilinear_series.radius p)) :=
  sorry

/-!
### Changing origin in a power series

If a function is analytic in a disk `D(x, R)`, then it is analytic in any disk contained in that
one. Indeed, one can write
$$
f (x + y + z) = \sum_{n} p_n (y + z)^n = \sum_{n, k} \binom{n}{k} p_n y^{n-k} z^k
= \sum_{k} \Bigl(\sum_{n} \binom{n}{k} p_n y^{n-k}\Bigr) z^k.
$$
The corresponding power series has thus a `k`-th coefficient equal to
$\sum_{n} \binom{n}{k} p_n y^{n-k}$. In the general case where `pₙ` is a multilinear map, this has
to be interpreted suitably: instead of having a binomial coefficient, one should sum over all
possible subsets `s` of `fin n` of cardinal `k`, and attribute `z` to the indices in `s` and
`y` to the indices outside of `s`.

In this paragraph, we implement this. The new power series is called `p.change_origin y`. Then, we
check its convergence and the fact that its sum coincides with the original sum. The outcome of this
discussion is that the set of points where a function is analytic is open.
-/

namespace formal_multilinear_series

/--
Changing the origin of a formal multilinear series `p`, so that
`p.sum (x+y) = (p.change_origin x).sum y` when this makes sense.

Here, we don't use the bracket notation `⟨n, s, hs⟩` in place of the argument `i` in the lambda,
as this leads to a bad definition with auxiliary `_match` statements,
but we will try to use pattern matching in lambdas as much as possible in the proofs below
to increase readability.
-/
def change_origin {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) (x : E) : formal_multilinear_series 𝕜 E F :=
  fun (k : ℕ) =>
    tsum
      fun (i : sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) =>
        continuous_multilinear_map.restr (p (sigma.fst i)) ↑(sigma.snd i) sorry x

/-- Auxiliary lemma controlling the summability of the sequence appearing in the definition of
`p.change_origin`, first version. -/
-- Note here and below it is necessary to use `@` and provide implicit arguments using `_`,

-- so that it is possible to use pattern matching in the lambda.

-- Overall this seems a good trade-off in readability.

theorem change_origin_summable_aux1 {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {x : E} {r : nnreal} (h : ↑(nnnorm x) + ↑r < radius p) :
    summable
        fun (_x : sigma fun (n : ℕ) => finset (fin n)) =>
          (fun (_a : sigma fun (n : ℕ) => finset (fin n)) =>
              sigma.cases_on _a
                fun (fst : ℕ) (snd : finset (fin fst)) =>
                  idRhs ℝ (norm (p fst) * norm x ^ (fst - finset.card snd) * ↑r ^ finset.card snd))
            _x :=
  sorry

/-- Auxiliary lemma controlling the summability of the sequence appearing in the definition of
`p.change_origin`, second version. -/
theorem change_origin_summable_aux2 {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {x : E} {r : nnreal} (h : ↑(nnnorm x) + ↑r < radius p) :
    summable
        fun
          (_x :
          sigma
            fun (k : ℕ) =>
              sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) =>
          (fun
              (_a :
              sigma
                fun (k : ℕ) =>
                  sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) =>
              sigma.cases_on _a
                fun (fst : ℕ)
                  (snd :
                  sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = fst) =>
                  sigma.cases_on snd
                    fun (snd_fst : ℕ)
                      (snd_snd : Subtype fun (s : finset (fin snd_fst)) => finset.card s = fst) =>
                      subtype.cases_on snd_snd
                        fun (snd_snd_val : finset (fin snd_fst))
                          (snd_snd_property : finset.card snd_snd_val = fst) =>
                          idRhs ℝ
                            (norm
                                (continuous_multilinear_map.restr (p snd_fst) snd_snd_val
                                  snd_snd_property x) *
                              ↑r ^ fst))
            _x :=
  sorry

/-- An auxiliary definition for `change_origin_radius`. -/
def change_origin_summable_aux_j (k : ℕ) :
    (sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) →
        sigma
          fun (k : ℕ) =>
            sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k :=
  fun (_x : sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) => sorry

theorem change_origin_summable_aux_j_injective (k : ℕ) :
    function.injective (change_origin_summable_aux_j k) :=
  sorry

/-- Auxiliary lemma controlling the summability of the sequence appearing in the definition of
`p.change_origin`, third version. -/
theorem change_origin_summable_aux3 {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {x : E} (k : ℕ) (h : ↑(nnnorm x) < radius p) :
    summable
        fun (_x : sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) =>
          (fun (_a : sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) =>
              sigma.cases_on _a
                fun (fst : ℕ) (snd : Subtype fun (s : finset (fin fst)) => finset.card s = k) =>
                  subtype.cases_on snd
                    fun (snd_val : finset (fin fst)) (snd_property : finset.card snd_val = k) =>
                      idRhs ℝ
                        (norm (continuous_multilinear_map.restr (p fst) snd_val snd_property x)))
            _x :=
  sorry

-- FIXME this causes a deterministic timeout with `-T50000`

/-- The radius of convergence of `p.change_origin x` is at least `p.radius - ∥x∥`. In other words,
`p.change_origin x` is well defined on the largest ball contained in the original ball of
convergence.-/
theorem change_origin_radius {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {x : E} :
    radius p - ↑(nnnorm x) ≤ radius (change_origin p x) :=
  sorry

-- From this point on, assume that the space is complete, to make sure that series that converge

-- in norm also converge in `F`.

/-- The `k`-th coefficient of `p.change_origin` is the sum of a summable series. -/
theorem change_origin_has_sum {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {x : E} [complete_space F] (k : ℕ)
    (h : ↑(nnnorm x) < radius p) :
    has_sum
        (fun (i : sigma fun (n : ℕ) => Subtype fun (s : finset (fin n)) => finset.card s = k) =>
          continuous_multilinear_map.restr (p (sigma.fst i)) (subtype.val (sigma.snd i))
            (subtype.property (sigma.snd i)) x)
        (change_origin p x k) :=
  sorry

/-- Summing the series `p.change_origin x` at a point `y` gives back `p (x + y)`-/
theorem change_origin_eval {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) {x : E} {y : E} [complete_space F]
    (h : ↑(nnnorm x) + ↑(nnnorm y) < radius p) :
    has_sum (fun (k : ℕ) => coe_fn (change_origin p x k) fun (i : fin k) => y)
        (formal_multilinear_series.sum p (x + y)) :=
  sorry

end formal_multilinear_series

/-- If a function admits a power series expansion `p` on a ball `B (x, r)`, then it also admits a
power series on any subball of this ball (even with a different center), given by `p.change_origin`.
-/
theorem has_fpower_series_on_ball.change_origin {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] [complete_space F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    {y : E} {r : ennreal} (hf : has_fpower_series_on_ball f p x r) (h : ↑(nnnorm y) < r) :
    has_fpower_series_on_ball f (formal_multilinear_series.change_origin p y) (x + y)
        (r - ↑(nnnorm y)) :=
  sorry

theorem has_fpower_series_on_ball.analytic_at_of_mem {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜]
    {E : Type u_2} [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F]
    [normed_space 𝕜 F] [complete_space F] {f : E → F} {p : formal_multilinear_series 𝕜 E F} {x : E}
    {y : E} {r : ennreal} (hf : has_fpower_series_on_ball f p x r) (h : y ∈ emetric.ball x r) :
    analytic_at 𝕜 f y :=
  sorry

theorem is_open_analytic_at (𝕜 : Type u_1) [nondiscrete_normed_field 𝕜] {E : Type u_2}
    [normed_group E] [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    [complete_space F] (f : E → F) : is_open (set_of fun (x : E) => analytic_at 𝕜 f x) :=
  sorry

end Mathlib


/-
Copyright (c) 2020 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Yury Kudryashov
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.analysis.analytic.basic
import Mathlib.analysis.special_functions.pow
import Mathlib.PostPort

universes u_1 u_2 u_3

namespace Mathlib

/-!
# Representation of `formal_multilinear_series.radius` as a `liminf`

In this file we prove that the radius of convergence of a `formal_multilinear_series` is equal to
$\liminf_{n\to\infty} \frac{1}{\sqrt[n]{∥p n∥}}$. This lemma can't go to `basic.lean` because this
would create a circular dependency once we redefine `exp` using `formal_multilinear_series`.
-/

namespace formal_multilinear_series

/-- The radius of a formal multilinear series is equal to
$\liminf_{n\to\infty} \frac{1}{\sqrt[n]{∥p n∥}}$. The actual statement uses `ℝ≥0` and some
coercions. -/
theorem radius_eq_liminf {𝕜 : Type u_1} [nondiscrete_normed_field 𝕜] {E : Type u_2} [normed_group E]
    [normed_space 𝕜 E] {F : Type u_3} [normed_group F] [normed_space 𝕜 F]
    (p : formal_multilinear_series 𝕜 E F) :
    radius p = filter.liminf filter.at_top fun (n : ℕ) => 1 / ↑(nnnorm (p n) ^ (1 / ↑n)) :=
  sorry

end Mathlib