/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import data.fintype.basic
import data.list.min_max
import logic.encodable.basic

/-!
# Denumerable types

This file defines denumerable (countably infinite) types as a typeclass extending `encodable`. This
is used to provide explicit encode/decode functions from and to `ℕ`, with the information that those
functions are inverses of each other.

## Implementation notes

This property already has a name, namely `α ≃ ℕ`, but here we are interested in using it as a
typeclass.
-/

/-- A denumerable type is (constructively) bijective with `ℕ`. Typeclass equivalent of `α ≃ ℕ`. -/
class denumerable (α : Type*) extends encodable α :=
(decode_inv : ∀ n, ∃ a ∈ decode n, encode a = n)

open nat

namespace denumerable

section
variables {α : Type*} {β : Type*} [denumerable α] [denumerable β]
open encodable

theorem decode_is_some (α) [denumerable α] (n : ℕ) :
  (decode α n).is_some :=
option.is_some_iff_exists.2 $
(decode_inv n).imp $ λ a, Exists.fst

/-- Returns the `n`-th element of `α` indexed by the decoding. -/
def of_nat (α) [f : denumerable α] (n : ℕ) : α :=
option.get (decode_is_some α n)

@[simp, priority 900]
theorem decode_eq_of_nat (α) [denumerable α] (n : ℕ) :
  decode α n = some (of_nat α n) :=
option.eq_some_of_is_some _

@[simp] theorem of_nat_of_decode {n b}
  (h : decode α n = some b) : of_nat α n = b :=
option.some.inj $ (decode_eq_of_nat _ _).symm.trans h

@[simp] theorem encode_of_nat (n) : encode (of_nat α n) = n :=
let ⟨a, h, e⟩ := decode_inv n in
by rwa [of_nat_of_decode h]

@[simp] theorem of_nat_encode (a) : of_nat α (encode a) = a :=
of_nat_of_decode (encodek _)

/-- A denumerable type is equivalent to `ℕ`. -/
def eqv (α) [denumerable α] : α ≃ ℕ :=
⟨encode, of_nat α, of_nat_encode, encode_of_nat⟩

@[priority 100] -- See Note [lower instance priority]
instance : infinite α := infinite.of_surjective _ (eqv α).surjective

/-- A type equivalent to `ℕ` is denumerable. -/
def mk' {α} (e : α ≃ ℕ) : denumerable α :=
{ encode := e,
  decode := some ∘ e.symm,
  encodek := λ a, congr_arg some (e.symm_apply_apply _),
  decode_inv := λ n, ⟨_, rfl, e.apply_symm_apply _⟩ }

/-- Denumerability is conserved by equivalences. This is transitivity of equivalence the denumerable
way. -/
def of_equiv (α) {β} [denumerable α] (e : β ≃ α) : denumerable β :=
{ decode_inv := λ n, by simp,
  ..encodable.of_equiv _ e }

@[simp] theorem of_equiv_of_nat (α) {β} [denumerable α] (e : β ≃ α)
  (n) : @of_nat β (of_equiv _ e) n = e.symm (of_nat α n) :=
by apply of_nat_of_decode; show option.map _ _ = _; simp

/-- All denumerable types are equivalent. -/
def equiv₂ (α β) [denumerable α] [denumerable β] : α ≃ β := (eqv α).trans (eqv β).symm

instance nat : denumerable ℕ := ⟨λ n, ⟨_, rfl, rfl⟩⟩

@[simp] theorem of_nat_nat (n) : of_nat ℕ n = n := rfl

/-- If `α` is denumerable, then so is `option α`. -/
instance option : denumerable (option α) := ⟨λ n, begin
  cases n,
  { refine ⟨none, _, encode_none⟩,
    rw [decode_option_zero, option.mem_def] },
  refine ⟨some (of_nat α n), _, _⟩,
  { rw [decode_option_succ, decode_eq_of_nat, option.map_some', option.mem_def] },
  rw [encode_some, encode_of_nat],
end⟩

/-- If `α` and `β` are denumerable, then so is their sum. -/
instance sum : denumerable (α ⊕ β) :=
⟨λ n, begin
  suffices : ∃ a ∈ @decode_sum α β _ _ n,
    encode_sum a = bit (bodd n) (div2 n), {simpa [bit_decomp]},
  simp [decode_sum]; cases bodd n; simp [decode_sum, bit, encode_sum]
end⟩

section sigma
variables {γ : α → Type*} [∀ a, denumerable (γ a)]

/-- A denumerable collection of denumerable types is denumerable. -/
instance sigma : denumerable (sigma γ) :=
⟨λ n, by simp [decode_sigma]; exact ⟨_, _, ⟨rfl, heq.rfl⟩, by simp⟩⟩

@[simp] theorem sigma_of_nat_val (n : ℕ) :
  of_nat (sigma γ) n = ⟨of_nat α (unpair n).1, of_nat (γ _) (unpair n).2⟩ :=
option.some.inj $
by rw [← decode_eq_of_nat, decode_sigma_val]; simp; refl

end sigma

/-- If `α` and `β` are denumerable, then so is their product. -/
instance prod : denumerable (α × β) :=
of_equiv _ (equiv.sigma_equiv_prod α β).symm

@[simp] theorem prod_of_nat_val (n : ℕ) :
  of_nat (α × β) n = (of_nat α (unpair n).1, of_nat β (unpair n).2) :=
by simp; refl

@[simp] theorem prod_nat_of_nat : of_nat (ℕ × ℕ) = unpair :=
by funext; simp

instance int : denumerable ℤ := denumerable.mk' equiv.int_equiv_nat

instance pnat : denumerable ℕ+ := denumerable.mk' equiv.pnat_equiv_nat

/-- The lift of a denumerable type is denumerable. -/
instance ulift : denumerable (ulift α) := of_equiv _ equiv.ulift

/-- The lift of a denumerable type is denumerable. -/
instance plift : denumerable (plift α) := of_equiv _ equiv.plift

/-- If `α` is denumerable, then `α × α` and `α` are equivalent. -/
def pair : α × α ≃ α := equiv₂ _ _

end
end denumerable

namespace nat.subtype
open function encodable

/-! ### Subsets of `ℕ` -/

variables {s : set ℕ} [infinite s]

section classical
open_locale classical

end classical

variable [decidable_pred (∈ s)]

/-- Returns the next natural in a set, according to the usual ordering of `ℕ`. -/
def succ (x : s) : s :=
have h : ∃ m, ↑x + m + 1 ∈ s, from exists_succ x,
⟨↑x + nat.find h + 1, nat.find_spec h⟩

lemma succ_le_of_lt {x y : s} (h : y < x) : succ y ≤ x :=
have hx : ∃ m, ↑y + m + 1 ∈ s, from exists_succ _,
let ⟨k, hk⟩ := nat.exists_eq_add_of_lt h in
have nat.find hx ≤ k, from nat.find_min' _ (hk ▸ x.2),
show (y : ℕ) + nat.find hx + 1 ≤ x,
by rw hk; exact add_le_add_right (add_le_add_left this _) _

lemma le_succ_of_forall_lt_le {x y : s} (h : ∀ z < x, z ≤ y) : x ≤ succ y :=
have hx : ∃ m, ↑y + m + 1 ∈ s, from exists_succ _,
show ↑x ≤ ↑y + nat.find hx + 1,
from le_of_not_gt $ λ hxy,
(h ⟨_, nat.find_spec hx⟩ hxy).not_lt $
  calc ↑y ≤ ↑y + nat.find hx : le_add_of_nonneg_right (nat.zero_le _)
  ... < ↑y + nat.find hx + 1 : nat.lt_succ_self _

lemma lt_succ_self (x : s) : x < succ x :=
calc (x : ℕ) ≤ x + _ : le_self_add
... < succ x : nat.lt_succ_self (x + _)

lemma lt_succ_iff_le {x y : s} : x < succ y ↔ x ≤ y :=
⟨λ h, le_of_not_gt (λ h', not_le_of_gt h (succ_le_of_lt h')),
  λ h, lt_of_le_of_lt h (lt_succ_self _)⟩

/-- Returns the `n`-th element of a set, according to the usual ordering of `ℕ`. -/
def of_nat (s : set ℕ) [decidable_pred (∈ s)] [infinite s] : ℕ → s
| 0     := ⊥
| (n+1) := succ (of_nat n)

lemma of_nat_surjective_aux : ∀ {x : ℕ} (hx : x ∈ s), ∃ n, of_nat s n = ⟨x, hx⟩
| x := λ hx, let t : list s := ((list.range x).filter (λ y, y ∈ s)).pmap
  (λ (y : ℕ) (hy : y ∈ s), ⟨y, hy⟩) (by simp) in
have hmt : ∀ {y : s}, y ∈ t ↔ y < ⟨x, hx⟩,
  by simp [list.mem_filter, subtype.ext_iff_val, t]; intros; refl,
have wf : ∀ m : s, list.maximum t = m → ↑m < x,
  from λ m hmax, by simpa [hmt] using list.maximum_mem hmax,
begin
  cases hmax : list.maximum t with m,
  { exact ⟨0, le_antisymm bot_le
      (le_of_not_gt (λ h, list.not_mem_nil (⊥ : s) $
        by rw [← list.maximum_eq_none.1 hmax, hmt]; exact h))⟩ },
  cases of_nat_surjective_aux m.2 with a ha,
  exact ⟨a + 1, le_antisymm
    (by rw of_nat; exact succ_le_of_lt (by rw ha; exact wf _ hmax)) $
    by rw of_nat; exact le_succ_of_forall_lt_le
      (λ z hz, by rw ha; cases m; exact list.le_maximum_of_mem (hmt.2 hz) hmax)⟩
end
using_well_founded {dec_tac := `[tauto]}

lemma of_nat_surjective : surjective (of_nat s) :=
λ ⟨x, hx⟩, of_nat_surjective_aux hx

@[simp] lemma of_nat_range : set.range (of_nat s) = set.univ := of_nat_surjective.range_eq

@[simp] lemma coe_comp_of_nat_range : set.range (coe ∘ of_nat s : ℕ → ℕ) = s :=
by rw [set.range_comp coe, of_nat_range, set.image_univ, subtype.range_coe]

private def to_fun_aux (x : s) : ℕ :=
(list.range x).countp (∈ s)

private lemma to_fun_aux_eq (x : s) :
  to_fun_aux x = ((finset.range x).filter (∈ s)).card :=
by rw [to_fun_aux, list.countp_eq_length_filter]; refl

open finset

private lemma right_inverse_aux : ∀ n, to_fun_aux (of_nat s n) = n
| 0 := begin
  rw [to_fun_aux_eq, card_eq_zero, eq_empty_iff_forall_not_mem],
  rintro n hn,
  rw [mem_filter, of_nat, mem_range] at hn,
  exact bot_le.not_lt (show (⟨n, hn.2⟩ : s) < ⊥, from hn.1),
end
| (n+1) := have ih : to_fun_aux (of_nat s n) = n, from right_inverse_aux n,
have h₁ : (of_nat s n : ℕ) ∉ (range (of_nat s n)).filter (∈ s), by simp,
have h₂ : (range (succ (of_nat s n))).filter (∈ s) =
  insert (of_nat s n) ((range (of_nat s n)).filter (∈ s)),
  begin
    simp only [finset.ext_iff, mem_insert, mem_range, mem_filter],
    exact λ m, ⟨λ h, by simp only [h.2, and_true]; exact or.symm
        (lt_or_eq_of_le ((@lt_succ_iff_le _ _ _ ⟨m, h.2⟩ _).1 h.1)),
      λ h, h.elim (λ h, h.symm ▸ ⟨lt_succ_self _, (of_nat s n).prop⟩)
        (λ h, ⟨h.1.trans (lt_succ_self _), h.2⟩)⟩,
  end,
begin
  simp only [to_fun_aux_eq, of_nat, range_succ] at ⊢ ih,
  conv {to_rhs, rw [← ih, ← card_insert_of_not_mem h₁, ← h₂] },
end

/-- Any infinite set of naturals is denumerable. -/
def denumerable (s : set ℕ) [decidable_pred (∈ s)] [infinite s] : denumerable s :=
denumerable.of_equiv ℕ
{ to_fun := to_fun_aux,
  inv_fun := of_nat s,
  left_inv := left_inverse_of_surjective_of_right_inverse of_nat_surjective right_inverse_aux,
  right_inv := right_inverse_aux }

end nat.subtype

namespace denumerable
open encodable

/-- An infinite encodable type is denumerable. -/
def of_encodable_of_infinite (α : Type*) [encodable α] [infinite α] : denumerable α :=
begin
  letI := @decidable_range_encode α _;
  letI : infinite (set.range (@encode α _)) :=
    infinite.of_injective _ (equiv.of_injective _ encode_injective).injective,
  letI := nat.subtype.denumerable (set.range (@encode α _)),
  exact denumerable.of_equiv (set.range (@encode α _)) (equiv_range_encode α),
end

end denumerable


import basic
import split_cycle

structure election_profile (χ υ : Type*) :=
(cands : finset χ)
(cpos : 0 < cands.card)
(voters : finset υ)
(vpos : 0 < voters.card)
(Q : υ → χ → χ → Prop)

/-  N "removing b from N"
    a : (N.remove b)
def margin {χ υ : Type*} (voters : finset υ) (Q : υ → χ → χ → Prop) (c c' : χ)
  [∀ v, decidable_rel (Q v)] : ℤ :=
(voters.filter (λ v, Q v c c')).card - (voters.filter (λ v, Q v c' c)).card
-/

instance {α : Type*} (s : finset α) : decidable s.nonempty :=
begin
  rw ←finset.card_pos,
  apply_instance,
end

def best_margin {χ υ : Type*} (voters : finset υ) (s : finset (χ × χ)) (Q : υ → χ → χ → Prop)
  [∀ v, decidable_rel (Q v)] : ℤ :=
  if hn : s.nonempty
    then s.sup' hn (λ p, margin voters Q p.1 p.2)
  else 0

def uniquely_weighted {χ υ : Type*} --
  (voters : finset υ) (cands : finset χ) (Q : υ → χ → χ → Prop)
  [∀ v, decidable_rel (Q v)] : Prop :=
∀ a b a' b' ∈ cands, margin voters Q a b ≠ margin voters Q a' b'

/-
def uniquely_weighted {χ υ : Type*} --
  (voters : finset υ) (cands : finset χ) (Q : υ → χ → χ → Prop)
  [∀ v, decidable_rel (Q v)] : Prop :=
∀ a b a' b' ∈ cands, ((a ≠ a' ) ∨ (b ≠ b')) →
  margin voters Q a b ≠ margin voters Q a' b'
-/

def simple_stable_voting' {χ υ : Type*} (voters : finset υ) (Q : υ → χ → χ → Prop)
  [decidable_eq χ]
  [∀ v, decidable_rel (Q v)] :
  Π (n : ℕ) (cands : finset χ) (hn : cands.card = n) (cpos : 0 < n), finset χ
| 0 _ _ cpos := (nat.not_lt_zero _ cpos).elim
| 1 cands _ _ := cands
| (n+2) cands hn cpos :=
let
  -- whether c wins when candidate rem is removed
  still_wins (c rem : χ) : Prop :=
    if rem_prop : rem ∈ cands
      then c ∈ simple_stable_voting' (n+1) (cands.erase rem)
          (by { rw [finset.card_erase_of_mem, hn]; simp [rem_prop], })
          (by omega)
    else false,
  viable : finset (χ × χ) := (cands.product cands).filter
      (λ p, still_wins p.1 p.2 )
in finset.image prod.fst $ viable.filter (λ p, (margin voters Q p.1 p.2 = best_margin voters viable Q))

def simple_stable_voting {χ υ : Type*} (prof : election_profile χ υ)
  [decidable_eq χ] [∀ v, decidable_rel (prof.Q v)] : finset χ :=
simple_stable_voting' prof.voters prof.Q prof.cands.card prof.cands rfl prof.cpos

lemma ssv_singleton {χ υ : Type*} (prof : election_profile χ υ) (hcands : prof.cands.card = 1)
  [decidable_eq χ] [∀ v, decidable_rel (prof.Q v)] :
  simple_stable_voting prof = prof.cands :=
by simp only [simple_stable_voting, simple_stable_voting', hcands]

lemma exists_best_margin {χ υ : Type*} {s : finset (χ × χ)} (voters : finset υ) (Q : υ → χ → χ → Prop)
  [∀ v, decidable_rel (Q v)] (hs : s.nonempty) :
  ∃ p : χ × χ, p ∈ s ∧ margin voters Q p.1 p.2 = best_margin voters s Q :=
begin
  obtain ⟨b, b_in, hb⟩ := finset.exists_mem_eq_sup' hs
    (λ (p : χ × χ), margin voters Q p.fst p.snd),
  unfold best_margin,
  simp only [hs, dif_pos],
  exact ⟨b, b_in, hb.symm⟩,
end

open_locale classical

lemma exists_eq_plus_two (n : ℕ) (hn₀ : ¬ n = 0) (hn₁ : ¬ n = 1) :
  ∃ m, n = m + 2 :=
begin
  cases n with d, { exact (hn₀ nat.nat_zero_eq_zero).elim, },
  induction d with d hd, {exact (hn₁ rfl).elim, },
  use d,
end

lemma ge_two_of_ne_zero_ne_one {n : ℕ} (hn₀ : ¬ n = 0) (hn₁ : ¬ n = 1) :
  2 ≤ n :=
begin
  cases n with d, { exact (hn₀ nat.nat_zero_eq_zero).elim, },
  induction d with d hd, {exact (hn₁ rfl).elim, },
  rw nat.succ_le_iff,
  exact nat.one_lt_succ_succ d,
end

lemma mem_cands_of_mem_winners {χ υ : Type*} (prof : election_profile χ υ) (a : χ)
  [decidable_eq χ] [∀ v, decidable_rel (prof.Q v)] :
  a ∈ simple_stable_voting prof → a ∈ prof.cands :=
begin
  intro a_in,
  by_cases hcands : prof.cands.card = 1,
  { rwa ssv_singleton prof hcands at a_in, },
  obtain ⟨n, hn⟩ := exists_eq_plus_two prof.cands.card
    (by linarith [prof.cpos]) hcands,
  simp only [simple_stable_voting, simple_stable_voting', hn,
    exists_prop, exists_and_distrib_right, exists_eq_right,
    finset.mem_image, finset.mem_filter, finset.filter_congr_decidable,
    prod.exists, finset.mem_product] at a_in,
  rcases a_in with ⟨b,⟨⟨p_in,hp⟩,ha⟩⟩,
  exact p_in.1,
end

lemma exists_ssv_winner' {χ υ : Type*} :
  ∀ (n : ℕ) (prof : election_profile χ υ), prof.cands.card = n →
  ∃ a, a ∈ simple_stable_voting prof :=
begin
  intro n,
  cases n with d,
  { intros prof hn,
    linarith [hn, prof.cpos], },
  induction d with d IH,
  { intros prof hn,
    rw ssv_singleton prof hn,
    exact finset.card_pos.1 prof.cpos, },
  intros prof hm,
  set m := d.succ with m_succ,
  obtain ⟨b, b_in⟩ := finset.card_pos.1 prof.cpos,
  have h_erase_card : ∀ z ∈ prof.cands, finset.card (prof.cands.erase z) = m,
  { intros z z_in,
    rw finset.card_erase_of_mem z_in,
    exact nat.pred_eq_of_eq_succ hm, },
  set prof' : election_profile χ υ :=
    ⟨(prof.cands.erase b),
     (by rw (h_erase_card b b_in); omega),
     prof.voters, prof.vpos, prof.Q⟩ with h_prof',
  obtain ⟨a, a_in⟩ := IH prof' (by rw ← h_erase_card b b_in),
  have card_eq_d : prof.cands.card = d + 2 := by rw hm,
  simp only [simple_stable_voting, simple_stable_voting',
    card_eq_d, exists_prop, exists_and_distrib_right, exists_eq_right,
    finset.mem_image, finset.mem_filter, finset.filter_congr_decidable,
    prod.exists, finset.mem_product],
  set viable_set : finset (χ × χ) := (prof.cands.product prof.cands).filter
    (λ p, if p2_in : p.2 ∈ prof.cands
      then p.1 ∈ simple_stable_voting' prof.voters prof.Q d.succ
           (prof.cands.erase p.2)
           (by rwa h_erase_card p.snd p2_in) (nat.succ_pos d)
      else false) with hvs,
  have viable_nonempty : viable_set.nonempty,
  { use ⟨a,b⟩,
    have : a ∈ (prof.cands.erase b) :=
      by simp only [mem_cands_of_mem_winners prof' a a_in],
    simp only [simple_stable_voting, h_prof', h_erase_card b b_in, m_succ] at a_in,
    simp only [finset.mem_filter, finset.mem_product, b_in,
      dif_pos, dif_pos, and_true, finset.mem_filter, finset.mem_product],
    exact ⟨(finset.mem_erase.1 this).2, a_in⟩, },
  obtain ⟨p, p_in, hp⟩ := exists_best_margin prof.voters prof.Q viable_nonempty,
  refine ⟨p.1, p.2, ⟨_,(by rw hp)⟩⟩,
  rw [hvs, finset.mem_filter] at p_in,
  cases p_in with hp₁ hp₂,
  refine ⟨finset.mem_product.1 hp₁, _⟩,
  simpa only [(finset.mem_product.mp hp₁).right, dif_pos, finset.mem_product,
    true_and, dif_pos, finset.mem_product, true_and,
    dif_pos, finset.mem_product] using hp₂,
end

lemma exists_ssv_winner {χ υ : Type*} (prof : election_profile χ υ) :
  ∃ a, a ∈ simple_stable_voting prof := exists_ssv_winner' prof.cands.card prof rfl

lemma ssv_winner_unique' {χ υ : Type*} :
  ∀ (n : ℕ) (prof : election_profile χ υ), prof.cands.card = n →
  uniquely_weighted prof.voters prof.cands prof.Q →
  (simple_stable_voting prof).card = 1 :=
begin
  intro n,
  cases n with d,
  { intros prof hn,
    linarith [hn, prof.cpos], },
  induction d with d IH,
  { intros prof hn _,
    rwa ssv_singleton prof hn, },
  set m := d.succ with m_succ,
  intros prof hm h_uniq,
  have card_eq_d : prof.cands.card = d + 2 := by rw hm,
  by_contradiction h_card,
  have hab : ∃ a b ∈ (simple_stable_voting prof), a ≠ b,
  { obtain ⟨a, a_in⟩ := exists_ssv_winner prof,
    suffices : 2 ≤ (simple_stable_voting prof).card,
    { obtain ⟨b, b_in, hb⟩ := finset.exists_second_distinct_mem this a_in,
      use ⟨a, b, a_in, b_in, hb.symm⟩, },
    exact ge_two_of_ne_zero_ne_one
      (ne_of_gt (finset.card_pos.2 (exists_ssv_winner prof))) h_card, },
  rcases hab with ⟨a, b, a_in, b_in, a_neq_b⟩,
  simp only [simple_stable_voting, simple_stable_voting',
    card_eq_d, exists_prop, exists_and_distrib_right, exists_eq_right,
    finset.mem_image, finset.mem_filter, finset.filter_congr_decidable,
    prod.exists, finset.mem_product] at a_in b_in,
  rcases a_in with ⟨x,⟨⟨p_in,hp⟩,ha⟩⟩, rcases b_in with ⟨y,⟨⟨q_in,hq⟩,hb⟩⟩,
  rw ← ha at hb,
  exact (h_uniq b y a x q_in.1 q_in.2 p_in.1 p_in.2) hb,
end

lemma ssv_winner_unique {χ υ : Type*} (prof : election_profile χ υ)
  (h_uniq : uniquely_weighted prof.voters prof.cands prof.Q) :
  (simple_stable_voting prof).card = 1 :=
ssv_winner_unique' prof.cands.card prof rfl h_uniq


import data.real.basic
import data.set.lattice
import topology.basic
import game.topology.union_closed_sets

open set

--begin hide
namespace xena
-- Work in progress
-- end hide

-- begin hide
-- Checking mathlib definitions
variable β : Type*
-- end hide

/- Lemma
Arbitrary intersection of closed sets is closed -- WIP, to do.
-/
lemma is_closed_inter_of_closed (X : β → set ℝ ) ( hj : ∀ j, is_closed (X j) )
    : is_closed (Inter X) :=
begin
    sorry,
end

end xena -- hide


/-
Copyright (c) 2021 Joël Riou. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joël Riou, Adam Topaz, Johan Commelin
-/

import algebra.homology.additive
import algebraic_topology.Moore_complex
import algebra.big_operators.fin
import category_theory.preadditive.opposite
import category_theory.idempotents.functor_categories
import tactic.equiv_rw

/-!

# The alternating face map complex of a simplicial object in a preadditive category

We construct the alternating face map complex, as a
functor `alternating_face_map_complex : simplicial_object C ⥤ chain_complex C ℕ`
for any preadditive category `C`. For any simplicial object `X` in `C`,
this is the homological complex `... → X_2 → X_1 → X_0`
where the differentials are alternating sums of faces.

The dual version `alternating_coface_map_complex : cosimplicial_object C ⥤ cochain_complex C ℕ`
is also constructed.

We also construct the natural transformation
`inclusion_of_Moore_complex : normalized_Moore_complex A ⟶ alternating_face_map_complex A`
when `A` is an abelian category.

## References
* https://stacks.math.columbia.edu/tag/0194
* https://ncatlab.org/nlab/show/Moore+complex

-/

open category_theory category_theory.limits category_theory.subobject
open category_theory.preadditive category_theory.category category_theory.idempotents
open opposite

open_locale big_operators
open_locale simplicial

noncomputable theory

namespace algebraic_topology

namespace alternating_face_map_complex

/-!
## Construction of the alternating face map complex
-/

variables {C : Type*} [category C] [preadditive C]
variables (X : simplicial_object C)
variables (Y : simplicial_object C)

/-- The differential on the alternating face map complex is the alternate
sum of the face maps -/
@[simp]
def obj_d (n : ℕ) : X _[n+1] ⟶ X _[n] :=
∑ (i : fin (n+2)), (-1 : ℤ)^(i : ℕ) • X.δ i

/--
## The chain complex relation `d ≫ d`
-/
lemma d_squared (n : ℕ) : obj_d X (n+1) ≫ obj_d X n = 0 :=
begin
  /- we start by expanding d ≫ d as a double sum -/
  dsimp,
  rw comp_sum,
  let d_l := λ (j : fin (n+3)), (-1 : ℤ)^(j : ℕ) • X.δ j,
  let d_r := λ (i : fin (n+2)), (-1 : ℤ)^(i : ℕ) • X.δ i,
  rw [show (λ i , (∑ j : fin (n+3), d_l j) ≫ d_r i) =
    (λ i, ∑ j : fin (n+3), (d_l j ≫ d_r i)), by { ext i, rw sum_comp, }],
  rw ← finset.sum_product',
  /- then, we decompose the index set P into a subet S and its complement Sᶜ -/
  let P := fin (n+2) × fin (n+3),
  let S := finset.univ.filter (λ (ij : P), (ij.2 : ℕ) ≤ (ij.1 : ℕ)),
  let term := λ (ij : P), d_l ij.2 ≫ d_r ij.1,
  erw [show ∑ (ij : P), term ij =
    (∑ ij in S, term ij) + (∑ ij in Sᶜ, term ij), by rw finset.sum_add_sum_compl],
  rw [← eq_neg_iff_add_eq_zero, ← finset.sum_neg_distrib],
  /- we are reduced to showing that two sums are equal, and this is obtained
  by constructing a bijection φ : S -> Sᶜ, which maps (i,j) to (j,i+1),
  and by comparing the terms -/
  let φ : Π (ij : P), ij ∈ S → P := λ ij hij,
    (fin.cast_lt ij.2
      (lt_of_le_of_lt (finset.mem_filter.mp hij).right (fin.is_lt ij.1)), ij.1.succ),
  apply finset.sum_bij φ,
  { -- φ(S) is contained in Sᶜ
    intros ij hij,
    simp only [finset.mem_univ, finset.compl_filter, finset.mem_filter, true_and,
      fin.coe_succ, fin.coe_cast_lt] at hij ⊢,
    linarith, },
  { /- identification of corresponding terms in both sums -/
    rintro ⟨i, j⟩ hij,
    simp only [term, d_l, d_r, φ, comp_zsmul, zsmul_comp, ← neg_smul, ← mul_smul,
      pow_add, neg_mul, mul_one, fin.coe_cast_lt,
      fin.coe_succ, pow_one, mul_neg, neg_neg],
    let jj : fin (n+2) := (φ (i,j) hij).1,
    have ineq : jj ≤ i, { rw ← fin.coe_fin_le, simpa using hij, },
    rw [category_theory.simplicial_object.δ_comp_δ X ineq, fin.cast_succ_cast_lt, mul_comm] },
  { -- φ : S → Sᶜ is injective
    rintro ⟨i, j⟩ ⟨i', j'⟩ hij hij' h,
    rw [prod.mk.inj_iff],
    refine ⟨by simpa using congr_arg prod.snd h, _⟩,
    have h1 := congr_arg fin.cast_succ (congr_arg prod.fst h),
    simpa [fin.cast_succ_cast_lt] using h1 },
  { -- φ : S → Sᶜ is surjective
    rintro ⟨i', j'⟩ hij',
    simp only [true_and, finset.mem_univ, finset.compl_filter, not_le,
      finset.mem_filter] at hij',
    refine ⟨(j'.pred _, fin.cast_succ i'), _, _⟩,
    { intro H,
      simpa only [H, nat.not_lt_zero, fin.coe_zero] using hij' },
    { simpa only [true_and, finset.mem_univ, fin.coe_cast_succ, fin.coe_pred,
        finset.mem_filter] using nat.le_pred_of_lt hij', },
    { simp only [prod.mk.inj_iff, fin.succ_pred, fin.cast_lt_cast_succ],
      split; refl }, },
end

/-!
## Construction of the alternating face map complex functor
-/

/-- The alternating face map complex, on objects -/
def obj : chain_complex C ℕ := chain_complex.of (λ n, X _[n]) (obj_d X) (d_squared X)

@[simp]
lemma obj_X (X : simplicial_object C) (n : ℕ) :
  (alternating_face_map_complex.obj X).X n = X _[n] := rfl

@[simp]
lemma obj_d_eq (X : simplicial_object C) (n : ℕ) :
  (alternating_face_map_complex.obj X).d (n+1) n =
  ∑ (i : fin (n+2)), (-1 : ℤ)^(i : ℕ) • X.δ i :=
by apply chain_complex.of_d

variables {X} {Y}

/-- The alternating face map complex, on morphisms -/
def map (f : X ⟶ Y) : obj X ⟶ obj Y :=
chain_complex.of_hom _ _ _ _ _ _
  (λ n, f.app (op [n]))
  (λ n,
    begin
      dsimp,
      rw [comp_sum, sum_comp],
      apply finset.sum_congr rfl (λ x h, _),
      rw [comp_zsmul, zsmul_comp],
      congr' 1,
      symmetry,
      apply f.naturality,
    end)

@[simp]
lemma map_f (f : X ⟶ Y) (n : ℕ) : (map f).f n = f.app (op [n]) := rfl

end alternating_face_map_complex

variables (C : Type*) [category C] [preadditive C]

/-- The alternating face map complex, as a functor -/
def alternating_face_map_complex : simplicial_object C ⥤ chain_complex C ℕ :=
{ obj := alternating_face_map_complex.obj,
  map := λ X Y f, alternating_face_map_complex.map f }

variable {C}

@[simp]
lemma alternating_face_map_complex_obj_X (X : simplicial_object C) (n : ℕ) :
  ((alternating_face_map_complex C).obj X).X n = X _[n] := rfl

@[simp]
lemma alternating_face_map_complex_obj_d (X : simplicial_object C) (n : ℕ) :
  ((alternating_face_map_complex C).obj X).d (n+1) n =
  alternating_face_map_complex.obj_d X n :=
by apply chain_complex.of_d

@[simp]
lemma alternating_face_map_complex_map_f {X Y : simplicial_object C} (f : X ⟶ Y) (n : ℕ) :
  ((alternating_face_map_complex C).map f).f n = f.app (op [n]) := rfl

lemma map_alternating_face_map_complex {D : Type*} [category D] [preadditive D]
  (F : C ⥤ D) [F.additive] :
  alternating_face_map_complex C ⋙ F.map_homological_complex _ =
  (simplicial_object.whiskering C D).obj F ⋙ alternating_face_map_complex D :=
begin
  apply category_theory.functor.ext,
  { intros X Y f,
    ext n,
    simp only [functor.comp_map, homological_complex.comp_f,
      alternating_face_map_complex_map_f, functor.map_homological_complex_map_f,
      homological_complex.eq_to_hom_f, eq_to_hom_refl, comp_id, id_comp,
      simplicial_object.whiskering_obj_map_app], },
  { intro X,
    apply homological_complex.ext,
    { rintros i j (rfl : j + 1 = i),
      dsimp only [functor.comp_obj],
      simpa only [functor.map_homological_complex_obj_d, alternating_face_map_complex_obj_d,
        eq_to_hom_refl, id_comp, comp_id, alternating_face_map_complex.obj_d,
        functor.map_sum, functor.map_zsmul], },
    { ext n,
      refl, }, },
end

lemma karoubi_alternating_face_map_complex_d (P : karoubi (simplicial_object C)) (n : ℕ) :
  (((alternating_face_map_complex.obj (karoubi_functor_category_embedding.obj P)).d (n+1) n).f) =
    P.p.app (op [n+1]) ≫ ((alternating_face_map_complex.obj P.X).d (n+1) n) :=
begin
  dsimp,
  simpa only [alternating_face_map_complex.obj_d_eq, karoubi.sum_hom,
    preadditive.comp_sum, karoubi.zsmul_hom, preadditive.comp_zsmul],
end

namespace alternating_face_map_complex

/-- The natural transformation which gives the augmentation of the alternating face map
complex attached to an augmented simplicial object. -/
@[simps]
def ε [limits.has_zero_object C] :
  simplicial_object.augmented.drop ⋙ algebraic_topology.alternating_face_map_complex C ⟶
  simplicial_object.augmented.point ⋙ chain_complex.single₀ C :=
{ app := λ X, begin
    equiv_rw chain_complex.to_single₀_equiv _ _,
    refine ⟨X.hom.app (op [0]), _⟩,
    dsimp,
    simp only [alternating_face_map_complex_obj_d, obj_d, fin.sum_univ_two,
      fin.coe_zero, pow_zero, one_zsmul, fin.coe_one, pow_one, neg_smul, add_comp,
      simplicial_object.δ_naturality, neg_comp],
    apply add_right_neg,
  end,
  naturality' := λ X Y f, by { ext, exact congr_app f.w _, }, }

end alternating_face_map_complex

/-!
## Construction of the natural inclusion of the normalized Moore complex
-/

variables {A : Type*} [category A] [abelian A]

/-- The inclusion map of the Moore complex in the alternating face map complex -/
def inclusion_of_Moore_complex_map (X : simplicial_object A) :
  (normalized_Moore_complex A).obj X ⟶ (alternating_face_map_complex A).obj X :=
chain_complex.of_hom _ _ _ _ _ _
  (λ n, (normalized_Moore_complex.obj_X X n).arrow)
  (λ n,
    begin
      /- we have to show the compatibility of the differentials on the alternating
         face map complex with those defined on the normalized Moore complex:
         we first get rid of the terms of the alternating sum that are obviously
         zero on the normalized_Moore_complex -/
      simp only [alternating_face_map_complex.obj_d],
      rw comp_sum,
      let t := λ (j : fin (n+2)), (normalized_Moore_complex.obj_X X (n+1)).arrow ≫
        ((-1 : ℤ)^(j : ℕ) • X.δ j),
      have def_t : (∀ j : fin (n+2), t j = (normalized_Moore_complex.obj_X X (n+1)).arrow ≫
        ((-1 : ℤ)^(j : ℕ) • X.δ j)) := by { intro j, refl, },
      rw [fin.sum_univ_succ t],
      have null : ∀ j : fin (n+1), t j.succ = 0,
      { intro j,
        rw [def_t, comp_zsmul, ← zsmul_zero ((-1 : ℤ)^(j.succ : ℕ))],
        apply congr_arg,
        rw normalized_Moore_complex.obj_X,
        rw ← factor_thru_arrow _ _
          (finset_inf_arrow_factors finset.univ _ j (by simp only [finset.mem_univ])),
        slice_lhs 2 3 { rw kernel_subobject_arrow_comp (X.δ j.succ), },
        simp only [comp_zero], },
      rw [fintype.sum_eq_zero _ null],
      simp only [add_zero],
      /- finally, we study the remaining term which is induced by X.δ 0 -/
      let eq := def_t 0,
      rw [show (-1 : ℤ)^((0 : fin (n+2)) : ℕ) = 1, by ring] at eq,
      rw one_smul at eq,
      rw eq,
      cases n; dsimp; simp,
    end)

@[simp]

variables (A)

/-- The inclusion map of the Moore complex in the alternating face map complex,
as a natural transformation -/
@[simps]
def inclusion_of_Moore_complex :
  (normalized_Moore_complex A) ⟶ (alternating_face_map_complex A) :=
{ app := inclusion_of_Moore_complex_map, }

namespace alternating_coface_map_complex

variables (X Y : cosimplicial_object C)

/-- The differential on the alternating coface map complex is the alternate
sum of the coface maps -/
@[simp]
def obj_d (n : ℕ) : X.obj [n] ⟶ X.obj [n+1] :=
∑ (i : fin (n+2)), (-1 : ℤ)^(i : ℕ) • X.δ i

lemma d_eq_unop_d (n : ℕ) :
  obj_d X n = (alternating_face_map_complex.obj_d
    ((cosimplicial_simplicial_equiv C).functor.obj (op X)) n).unop :=
by simpa only [obj_d, alternating_face_map_complex.obj_d, unop_sum, unop_zsmul]

lemma d_squared (n : ℕ) : obj_d X n ≫ obj_d X (n+1) = 0 :=
by simp only [d_eq_unop_d, ← unop_comp, alternating_face_map_complex.d_squared, unop_zero]

/-- The alternating coface map complex, on objects -/
def obj : cochain_complex C ℕ := cochain_complex.of (λ n, X.obj [n]) (obj_d X) (d_squared X)

variables {X} {Y}

/-- The alternating face map complex, on morphisms -/
@[simp]
def map (f : X ⟶ Y) : obj X ⟶ obj Y :=
cochain_complex.of_hom _ _ _ _ _ _
  (λ n, f.app [n])
  (λ n,
    begin
      dsimp,
      rw [comp_sum, sum_comp],
      apply finset.sum_congr rfl (λ x h, _),
      rw [comp_zsmul, zsmul_comp],
      congr' 1,
      symmetry,
      apply f.naturality,
    end)

end alternating_coface_map_complex

variable (C)

/-- The alternating coface map complex, as a functor -/
@[simps]
def alternating_coface_map_complex : cosimplicial_object C ⥤ cochain_complex C ℕ :=
{ obj := alternating_coface_map_complex.obj,
  map := λ X Y f, alternating_coface_map_complex.map f }

end algebraic_topology


import .proof_system_fin
import algebra.big_operators.basic

open_locale big_operators

section

/-!

This file contains classes for noninteractive proof systems.

-/

universe u
/-- The finite field parameter of our SNARK -/
variable {F : Type}
variable [field F]

-- The types of the statement and witness are assumed to be collections of n_stmt and n_wit field elements respectively.
-- variables {n_sample n_crs n_stmt n_wit n_proof : ℕ}
variables {n_stmt n_wit : ℕ}

-- def STMT := fin n_stmt -> F
-- def WIT := fin n_wit -> F

lemma lambda_mul {A B : Type u} [has_mul A] (a : A) (b : B → A) : (λ (i : B), a * b i) = (λ i, a) * b :=
begin
  ext i,
  rw pi.mul_apply,
end

-- def uniform_degree {σ F : Type*} [field F] (p : mv_polynomial σ F) (d : ℕ) : Prop :=

-- TODO mathlib
def uniform_degree {σ F : Type*} [field F] (p : mv_polynomial σ F) (d : ℕ) : Prop :=
∀ m ∈ p.support, finsupp.sum m (λ s k, k) = d

lemma mv_polynomial.bind₁_sum (T σ τ F : Type*) (S : finset T) [comm_semiring F] (f : σ → mv_polynomial τ F) (g : T -> mv_polynomial σ F) :
  mv_polynomial.bind₁ f (∑ s in S, g s) = (∑ s in S, mv_polynomial.bind₁ f (g s)) :=
begin
  exact (mv_polynomial.bind₁ f).map_sum (λ (x : T), g x) S,
end

example (T σ τ F : Type*) (A B : finset T) (f g : T -> F) [comm_semiring F] (h :  ∀ a ∈ A, f a = g a ) : ∑ a in A, f a = ∑ a in A, g a := by refine finset.sum_congr rfl h

-- todo mathlib
lemma uniform_degree_implies_bind₁_uniformity {σ τ F : Type*} [field F] (p : mv_polynomial σ F) (d : ℕ)
  (h : uniform_degree p d) (f : σ → mv_polynomial τ F) (a):
    mv_polynomial.bind₁ ((λ x, mv_polynomial.X a) * f) p = ((mv_polynomial.X a) ^ d) * mv_polynomial.bind₁ (f) p
:=
begin
  rw mv_polynomial.as_sum p,
  rw mv_polynomial.bind₁_sum,
  rw mv_polynomial.bind₁_sum,
  rw finset.mul_sum,
  rw finset.sum_congr,
  refl,
  intros y y_mem_support,
  unfold uniform_degree at h,
  replace h := h y y_mem_support,
  simp_rw mv_polynomial.bind₁_monomial,
  simp only [pi.mul_apply],
  simp_rw mul_pow,
  rw finset.prod_mul_distrib,
  simp_rw <-mul_assoc,
  congr' 1,
  rw mul_comm,
  congr' 1,
  rw <- h,
  rw finset.prod_pow_eq_pow_sum,
  congr' 1,
  -- ring_nf,
end

-- end

lemma mv_polynomial.mul_X_eq_zero {σ R : Type*} [comm_semiring R] (s : σ) (p : mv_polynomial σ R)(h : ((mv_polynomial.X s)) * p = 0) : p = 0 :=
begin
  rw mv_polynomial.ext_iff at h ⊢,
  intros m,
  replace h := h (m + finsupp.single s 1),
  simp at h,
  rw mul_comm at h,
  rw mv_polynomial.coeff_mul_X at h, -- generalize to X_pow
  simp [h],
end

-- todo mathlib
lemma mv_polynomial.mul_X_pow_eq_zero {σ R : Type*} [comm_semiring R] {s : σ} (d : ℕ) (p : mv_polynomial σ R)(h : ((mv_polynomial.X s) ^ d) * p = 0) : p = 0 :=
begin
  induction d,
  simp at h,
  exact h,
  apply d_ih,
  rw pow_succ at h,
  apply mv_polynomial.mul_X_eq_zero s,
  rw <-mul_assoc,
  exact h,

end

/-- Given a particular toxic waste sample, we can multiply this sample through all crs elems without affecting the soundness of the SNARK. This assumes that all checks have uniform degree as polynomials over the proof elements, (indeed for bilinear pairings, these polynomials will have degree 2) -/
noncomputable def change_exponent (𝓟 : AGM_proof_system F n_stmt n_wit)
  (sample : fin 𝓟.n_sample) (d : ℕ)
  (all_checks_uniform_degree : ∀ c ∈ 𝓟.polynomial_checks, uniform_degree c d) : AGM_proof_system F n_stmt n_wit :=
{ relation := 𝓟.relation,
  n_sample := 𝓟.n_sample,
  n_crs := 𝓟.n_crs,
  crs_elems := (λ i, mv_polynomial.X sample) * (𝓟.crs_elems),
  proof_elems_index := 𝓟.proof_elems_index,
  -- proof_crs_component := 𝓟.proof_crs_component,
  polynomial_checks := 𝓟.polynomial_checks,
  proof_element_checks := 𝓟.proof_element_checks,
  extractor := 𝓟.extractor,
  soundness :=
  begin
  -- sorry,
    rintros stmt agm ⟨poly_checks_pass', proof_elem_checks_pass'⟩,
    apply 𝓟.soundness,
    split,
    {
      intros c in_checks, -- f, -- f_never_zero,

      replace poly_checks_pass' := poly_checks_pass' c in_checks,
      simp at *,
      have : ⇑(mv_polynomial.bind₁ (λ (pf_idx : 𝓟.proof_elems_index), ∑ (x : fin 𝓟.n_crs), agm pf_idx x • (mv_polynomial.X sample * 𝓟.crs_elems x))) c
      =
      ((mv_polynomial.X sample) ^ d) * (⇑(mv_polynomial.bind₁ (λ (pf_idx : 𝓟.proof_elems_index), ∑ (x : fin 𝓟.n_crs), agm pf_idx x • (𝓟.crs_elems x))) c),
      {
        have funeq : (λ (pf_idx : 𝓟.proof_elems_index), ∑ (x : fin 𝓟.n_crs), agm pf_idx x • (mv_polynomial.X sample * 𝓟.crs_elems x))
        =
        (λ (pf_idx : 𝓟.proof_elems_index), ∑ (x : fin 𝓟.n_crs), mv_polynomial.X sample * agm pf_idx x • (𝓟.crs_elems x)),
        {
          funext,
          congr,
          funext,
          simp_rw mv_polynomial.smul_eq_C_mul,
          ring,
          -- library_search,
        },
        simp_rw funeq,
        simp_rw <-finset.mul_sum,
        apply uniform_degree_implies_bind₁_uniformity,
        apply all_checks_uniform_degree,
        apply in_checks,
        -- sorry,
      },
      rw this at poly_checks_pass',
      have foo := mv_polynomial.mul_X_pow_eq_zero _ _ poly_checks_pass',
      exact foo,
      -- rw mv_polynomial.eval
      -- exact poly_checks_pass',
      -- unfold function.comp at checks_give_zero ⊢,
      -- simp at *,
      -- simp_rw mv_polynomial.smul_eq_C_mul at *,
    },
    {
      intros idx val val_in,
      replace proof_elem_checks_pass' :=  proof_elem_checks_pass' idx val val_in,
      exact proof_elem_checks_pass',
    },
  end
}

lemma sum_conditional {S R : Type} [fintype S] [comm_ring R] (f : S -> R) (p : S -> Prop) [decidable_pred p] :
  ∑ (s : S), f s = (∑ (s : S), ite (p s) (f s) 0) + (∑ (s : S), ite (p s) 0 (f s)) :=
begin
  rw ←finset.sum_add_distrib,
  congr,
  funext,
  by_cases hp : p s,
  simp [hp],
  simp [hp],
end

lemma ite_or_eq_sum {S R : Type} [fintype S] [comm_ring R] {s a b : S} (h : a ≠ b) [decidable_eq S] (f : S -> R)  : ite (s = a ∨ s = b) (f s) 0 = (ite (s = a) (f a) 0) + (ite (s = b) (f b) 0) :=
begin
  by_cases ha : s = a,
  { by_cases hb : s = b,
    simp [h, ha, hb],
    exfalso,
    rw [<-ha, <-hb] at h,
    apply h,
    refl,
    simp [h, ha, hb],

    },
  { by_cases hb : s = b,
    simp [h, ha, hb],
    simp [h, ha, hb],
    },

end

lemma sum_or_two_eq {S R : Type} [fintype S] [comm_ring R] {a b : S} (h : a ≠ b) [decidable_eq S] (f : S -> R)  :
  ∑ (s : S), ite (s = a ∨ s = b) (f s) 0 = f a + f b :=
begin
  simp_rw [ite_or_eq_sum h], -- doesn't work without the h, why?
  rw finset.sum_add_distrib,
  congr,
  simp,
  simp,
end

example {S R : Type} [fintype S] [comm_ring R] (f : S -> R) (a : S) (p : Prop) [decidable p] : ite p a a = a :=
begin
  exact if_t_t p a
end

@[simp] lemma or_and_self_left (p q : Prop) : ((p ∨ q) ∧ q) ↔ q := by tauto
@[simp] lemma or_and_self_right (p q : Prop) : ((p ∨ q) ∧ p) ↔ p := by tauto
@[simp] lemma foobar1 (α : Type) (a b c : α) (h : b ≠ c) : ((a = b ∨ a = c) ∧ ¬ a = c) ↔ a = b :=
begin
  tidy,
end
@[simp] lemma foobar2 (α : Type) (a b c : α) (h : b ≠ c) : ((a = b ∨ a = c) ∧ ¬ a = b ∧ ¬ a = c) ↔ false :=
begin
  tidy,
end

-- example {A : Type} (a b : A) : a = b ↔ b = a := by {exact eq_comm}

/-- Adds one crs element to another and zeros out the added element. This might be useful in the case where in the given SNARK, this pair of CRS elements are always used with the same coefficient, in which case the resulting SNARK is complete. -/
noncomputable def collapse_crs_element (𝓟 : AGM_proof_system F n_stmt n_wit)
  (twin1 twin2 : fin 𝓟.n_crs) (not_same : twin1 ≠ twin2)
  (interchangeable :
    ∀ (stmt : fin n_stmt -> F) (val : (fin n_stmt → F) → fin 𝓟.n_crs → F) (idx : 𝓟.proof_elems_index)
        (val_in : val ∈ 𝓟.proof_element_checks idx) (agm : 𝓟.proof_elems_index → fin 𝓟.n_crs → F), val stmt = agm idx -> agm idx twin1 = agm idx twin2 ):
  AGM_proof_system F n_stmt n_wit :=
{ relation := 𝓟.relation,
  n_sample := 𝓟.n_sample,
  n_crs := 𝓟.n_crs,
  crs_elems := λ crs,
    if crs = twin1
      then 𝓟.crs_elems twin1 + 𝓟.crs_elems twin2
    else if crs = twin2
      then 0
    else 𝓟.crs_elems crs,
  proof_elems_index := 𝓟.proof_elems_index,
  polynomial_checks := 𝓟.polynomial_checks,
  proof_element_checks := 𝓟.proof_element_checks,
    -- λ idx,
    --   (𝓟.proof_element_checks idx).map
    --   (λ old stmt crs, (if crs = twin2 then old stmt twin1 else old stmt crs)) ,
  -- When the extractor goes to read the component of the second twin, it should instead read the first
  extractor := λ agm wit, 𝓟.extractor (λ proof_elem crs, agm proof_elem (if crs = twin2 then twin1 else crs)) wit,
  soundness :=
  begin
  -- sorry,
    rintros stmt agm ⟨poly_checks_pass', proof_elem_checks_pass'⟩,
    apply 𝓟.soundness,
    split,
    {
      intros c in_checks, -- f, -- f_never_zero,

      replace poly_checks_pass' := poly_checks_pass' c in_checks,
      -- simp at *,
      rw <-poly_checks_pass',
      apply congr, apply congr, refl, apply congr, refl,
      funext,
      simp_rw [mv_polynomial.smul_eq_C_mul],
      simp_rw [apply_ite (agm pf_idx)],
      simp_rw [apply_ite (mv_polynomial.C)],
      simp_rw ite_mul,
      simp_rw mul_ite,
      -- simp_rw [if_t_t p a],
      rw sum_conditional _ (λ x, x = twin1 ∨ x = twin2),
      nth_rewrite 2 sum_conditional _ (λ x, x = twin1 ∨ x = twin2),
      congr' 1,
      { simp_rw [sum_or_two_eq not_same],
        simp,
        simp_rw [@eq_comm _ twin2 twin1],
        simp [not_same],
        ring,
      },
      {
        congr' 1,
        funext s,
        simp,
        -- simp_rw finset.sum_ite_eq',
        -- simp only [finset.mem_univ, if_true],
        simp_rw ←ite_and,
        split_ifs,
        refl,
        contrapose! not_same,
        rw [<-h_1, <-h_2],
        contrapose! h, right, assumption,
        contrapose! h, left, assumption,
        refl,
        -- tidy,
      },
      refl,
    },
    {
      -- sorry,
      intros idx val val_in, -- need to pass in a different val
      replace proof_elem_checks_pass' :=  proof_elem_checks_pass' idx val val_in,
      rw proof_elem_checks_pass',
      funext,
      split_ifs,
      rotate,
      refl,
      rw [h],
      replace interchangeable := interchangeable stmt val idx val_in agm proof_elem_checks_pass',
      rw [interchangeable],
    },
  end
}

noncomputable def zero_and_carry {A : Type} (x : A →₀ ℕ) (source target : A) (factor : ℕ) : A →₀ ℕ :=
  x.erase source + finsupp.single target (factor * x source)

/-- This is the technical lemma on which the toxic waste collapse is based. We omit the proof. -/
lemma commutable_zero_and_carry {F proof_elems_index: Type} [field F] (n_sample n_crs : ℕ)
    (d d2 : ℕ)
    (sample_removed sample_target : fin n_sample)
    (multinomial: fin n_sample →₀ ℕ)
    (crs_elems : (fin n_crs) → (mv_polynomial (fin n_sample) F))
    (agm: proof_elems_index → fin n_crs → F) (c : mv_polynomial proof_elems_index F)
    (h : ∀ (crs_idx : fin n_crs), mv_polynomial.degree_of sample_target (crs_elems crs_idx) < d)
    (uniform_deg : uniform_degree c d2) :
  mv_polynomial.coeff multinomial ((mv_polynomial.aeval (λ (pf_idx : proof_elems_index), ∑ (crs_idx : fin n_crs), agm pf_idx crs_idx • crs_elems crs_idx)) c)
  =
  mv_polynomial.coeff (zero_and_carry multinomial sample_removed sample_target (d * d2)) ((mv_polynomial.aeval (λ (pf_idx : proof_elems_index), ∑ (x : fin n_crs), agm pf_idx x • mv_polynomial.eval₂ mv_polynomial.C (λ (x : fin n_sample), ite (x = sample_removed) (mv_polynomial.X sample_target ^ d) (mv_polynomial.X x)) (crs_elems x))) c) :=
begin
  sorry
end

-- Returns a SNARK where one fewer toxic waste element is actually used, replaced by sample_target ^ d
noncomputable def collapse_toxic_waste (𝓟 : AGM_proof_system F n_stmt n_wit) (d d2 : ℕ)
  (sample_removed sample_target : fin 𝓟.n_sample)
  (h : ∀ (crs_idx : fin 𝓟.n_crs), mv_polynomial.degree_of sample_target (𝓟.crs_elems crs_idx) < d)
  -- (all polychecks are of uniform degree d2)
  (uniform_deg : ∀ p ∈ 𝓟.polynomial_checks, uniform_degree p d2)
  :
  AGM_proof_system F n_stmt n_wit :=
{ relation := 𝓟.relation,
  n_sample := 𝓟.n_sample,
  n_crs := 𝓟.n_crs,
  crs_elems := (mv_polynomial.eval₂ (mv_polynomial.C)
    (λ x, if x = sample_removed then ((mv_polynomial.X sample_target) ^ (d))
      else mv_polynomial.X x)) ∘ 𝓟.crs_elems,
  proof_elems_index := 𝓟.proof_elems_index,
  -- proof_crs_component := 𝓟.proof_crs_component,
  polynomial_checks := 𝓟.polynomial_checks,
  proof_element_checks := 𝓟.proof_element_checks,
  extractor := 𝓟.extractor,
  soundness :=
  begin
    rintros stmt agm ⟨poly_checks_pass', proof_elem_checks_pass'⟩,
    apply 𝓟.soundness,
    split,
    {
      intros c in_checks,

      replace poly_checks_pass' := poly_checks_pass' c in_checks, -- f f_never_zero,
      simp at *,
      have all_coeffs_of_new_snark_zero :
        ∀ multinomial : fin 𝓟.n_sample →₀ ℕ,
          (⇑(mv_polynomial.bind₁ (λ (pf_idx : 𝓟.proof_elems_index), ∑ (x : fin 𝓟.n_crs), agm pf_idx x • mv_polynomial.eval₂ mv_polynomial.C (λ (x : fin 𝓟.n_sample), ite (x = sample_removed) (mv_polynomial.X sample_target ^ d) (mv_polynomial.X x)) (𝓟.crs_elems x))) c).coeff multinomial = 0,
      exact mv_polynomial.eq_zero_iff.mp poly_checks_pass', -- follows from extensionality on the polynomial
      have all_coeffs_of_old_snark_are_a_coeff_of_the_new : -- in particular, the coeff of a multinomial is what you get from zeroing out the sample_removed and multiplying this value by d * d2 and adding to sample target
        ∀ multinomial : fin 𝓟.n_sample →₀ ℕ,
          (⇑(mv_polynomial.bind₁ (λ (pf_idx : 𝓟.proof_elems_index), ∑ (crs_idx : fin 𝓟.n_crs), agm pf_idx crs_idx • 𝓟.crs_elems crs_idx)) c).coeff multinomial
            =
          (⇑(mv_polynomial.bind₁ (λ (pf_idx : 𝓟.proof_elems_index), ∑ (x : fin 𝓟.n_crs), agm pf_idx x • mv_polynomial.eval₂ mv_polynomial.C (λ (x : fin 𝓟.n_sample), ite (x = sample_removed) (mv_polynomial.X sample_target ^ d) (mv_polynomial.X x)) (𝓟.crs_elems x))) c).coeff (zero_and_carry multinomial sample_removed sample_target (d * d2)),
      {
        intro,
        clear all_coeffs_of_new_snark_zero,
        simp_rw [mv_polynomial.bind₁],
        apply commutable_zero_and_carry,
        apply h,
        apply uniform_deg,
        exact in_checks,
      },

      rw mv_polynomial.eq_zero_iff,
      intro d,
      rw all_coeffs_of_old_snark_are_a_coeff_of_the_new d,
      rw all_coeffs_of_new_snark_zero,

      -- thus all coeffs for old snark zero
      -- thus sound

    },
    {
      intros idx val val_in,
      replace proof_elem_checks_pass' :=  proof_elem_checks_pass' idx val val_in,
      exact proof_elem_checks_pass',
    },
  end
}

end


/-
Copyright (c) 2020 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.category_theory.limits.constructions.pullbacks
import Mathlib.category_theory.limits.shapes.biproducts
import Mathlib.category_theory.limits.shapes.images
import Mathlib.category_theory.abelian.non_preadditive
import Mathlib.PostPort

universes v u l

namespace Mathlib

/-!
# Abelian categories

This file contains the definition and basic properties of abelian categories.

There are many definitions of abelian category. Our definition is as follows:
A category is called abelian if it is preadditive,
has a finite products, kernels and cokernels,
and if every monomorphism and epimorphism is normal.

It should be noted that if we also assume coproducts, then preadditivity is
actually a consequence of the other properties, as we show in
`non_preadditive_abelian.lean`. However, this fact is of little practical
relevance, since essentially all interesting abelian categories come with a
preadditive structure. In this way, by requiring preadditivity, we allow the
user to pass in the preadditive structure the specific category they are
working with has natively.

## Main definitions

* `abelian` is the type class indicating that a category is abelian. It extends `preadditive`.
* `abelian.image f` is `kernel (cokernel.π f)`, and
* `abelian.coimage f` is `cokernel (kernel.ι f)`.

## Main results

* In an abelian category, mono + epi = iso.
* If `f : X ⟶ Y`, then the map `factor_thru_image f : X ⟶ image f` is an epimorphism, and the map
  `factor_thru_coimage f : coimage f ⟶ Y` is a monomorphism.
* Factoring through the image and coimage is a strong epi-mono factorisation. This means that
  * every abelian category has images. We instantiated this in such a way that `abelian.image f` is
    definitionally equal to `limits.image f`, and
  * there is a canonical isomorphism `coimage_iso_image : coimage f ≅ image f` such that
    `coimage.π f ≫ (coimage_iso_image f).hom ≫ image.ι f = f`. The lemma stating this is called
    `full_image_factorisation`.
* Every epimorphism is a cokernel of its kernel. Every monomorphism is a kernel of its cokernel.
* The pullback of an epimorphism is an epimorphism. The pushout of a monomorphism is a monomorphism.
  (This is not to be confused with the fact that the pullback of a monomorphism is a monomorphism,
  which is true in any category).

## Implementation notes

The typeclass `abelian` does not extend `non_preadditive_abelian`,
to avoid having to deal with comparing the two `has_zero_morphisms` instances
(one from `preadditive` in `abelian`, and the other a field of `non_preadditive_abelian`).
As a consequence, at the beginning of this file we trivially build
a `non_preadditive_abelian` instance from an `abelian` instance,
and use this to restate a number of theorems,
in each case just reusing the proof from `non_preadditive_abelian.lean`.

We don't show this yet, but abelian categories are finitely complete and finitely cocomplete.
However, the limits we can construct at this level of generality will most likely be less nice than
the ones that can be created in specific applications. For this reason, we adopt the following
convention:

* If the statement of a theorem involves limits, the existence of these limits should be made an
  explicit typeclass parameter.
* If a limit only appears in a proof, but not in the statement of a theorem, the limit should not
  be a typeclass parameter, but instead be created using `abelian.has_pullbacks` or a similar
  definition.

## References

* [F. Borceux, *Handbook of Categorical Algebra 2*][borceux-vol2]
* [P. Aluffi, *Algebra: Chaper 0*][aluffi2016]

-/

namespace category_theory

/--
A (preadditive) category `C` is called abelian if it has all finite products,
all kernels and cokernels, and if every monomorphism is the kernel of some morphism
and every epimorphism is the cokernel of some morphism.

(This definition implies the existence of zero objects:
finite products give a terminal object, and in a preadditive category
any terminal object is a zero object.)
-/
class abelian (C : Type u) [category C] extends preadditive C where
  has_finite_products : limits.has_finite_products C
  has_kernels : limits.has_kernels C
  has_cokernels : limits.has_cokernels C
  normal_mono : {X Y : C} → (f : X ⟶ Y) → [_inst_2 : mono f] → normal_mono f
  normal_epi : {X Y : C} → (f : X ⟶ Y) → [_inst_2 : epi f] → normal_epi f

end category_theory

namespace category_theory.abelian

/-- An abelian category has finite biproducts. -/
theorem has_finite_biproducts {C : Type u} [category C] [abelian C] :
    limits.has_finite_biproducts C :=
  limits.has_finite_biproducts.of_has_finite_products

/-- Every abelian category is, in particular, `non_preadditive_abelian`. -/
def non_preadditive_abelian {C : Type u} [category C] [abelian C] : non_preadditive_abelian C :=
  non_preadditive_abelian.mk normal_mono normal_epi

/-- In an abelian category, every epimorphism is strong. -/
theorem strong_epi_of_epi {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q)
    [epi f] : strong_epi f :=
  category_theory.strong_epi_of_regular_epi f

/-- In an abelian category, a monomorphism which is also an epimorphism is an isomorphism. -/
def is_iso_of_mono_of_epi {C : Type u} [category C] [abelian C] {X : C} {Y : C} (f : X ⟶ Y) [mono f]
    [epi f] : is_iso f :=
  is_iso_of_mono_of_strong_epi f

theorem mono_of_zero_kernel {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q)
    (R : C)
    (l :
      limits.is_limit
        (limits.kernel_fork.of_ι 0
          ((fun (this : 0 ≫ f = 0) => this)
            (eq.mpr
              (id
                (Eq.trans
                  ((fun (a a_1 : R ⟶ Q) (e_1 : a = a_1) (ᾰ ᾰ_1 : R ⟶ Q) (e_2 : ᾰ = ᾰ_1) =>
                      congr (congr_arg Eq e_1) e_2)
                    (0 ≫ f) 0 limits.zero_comp 0 0 (Eq.refl 0))
                  (propext (eq_self_iff_true 0))))
              trivial)))) :
    mono f :=
  non_preadditive_abelian.mono_of_zero_kernel f R l

theorem mono_of_kernel_ι_eq_zero {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q)
    (h : limits.kernel.ι f = 0) : mono f :=
  preadditive.mono_of_kernel_zero h

theorem epi_of_zero_cokernel {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q)
    (R : C)
    (l :
      limits.is_colimit
        (limits.cokernel_cofork.of_π 0
          ((fun (this : f ≫ 0 = 0) => this)
            (eq.mpr
              (id
                (Eq.trans
                  ((fun (a a_1 : P ⟶ R) (e_1 : a = a_1) (ᾰ ᾰ_1 : P ⟶ R) (e_2 : ᾰ = ᾰ_1) =>
                      congr (congr_arg Eq e_1) e_2)
                    (f ≫ 0) 0 limits.comp_zero 0 0 (Eq.refl 0))
                  (propext (eq_self_iff_true 0))))
              trivial)))) :
    epi f :=
  non_preadditive_abelian.epi_of_zero_cokernel f R l

theorem epi_of_cokernel_π_eq_zero {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q)
    (h : limits.cokernel.π f = 0) : epi f :=
  sorry

namespace images

/-- The kernel of the cokernel of `f` is called the image of `f`. -/
protected def image {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) : C :=
  limits.kernel (limits.cokernel.π f)

/-- The inclusion of the image into the codomain. -/
protected def image.ι {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) :
    images.image f ⟶ Q :=
  limits.kernel.ι (limits.cokernel.π f)

/-- There is a canonical epimorphism `p : P ⟶ image f` for every `f`. -/
protected def factor_thru_image {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) :
    P ⟶ images.image f :=
  limits.kernel.lift (limits.cokernel.π f) f sorry

/-- `f` factors through its image via the canonical morphism `p`. -/
@[simp] theorem image.fac_assoc {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q)
    {X' : C} (f' : Q ⟶ X') : images.factor_thru_image f ≫ image.ι f ≫ f' = f ≫ f' :=
  sorry

/-- The map `p : P ⟶ image f` is an epimorphism -/
protected instance factor_thru_image.category_theory.epi {C : Type u} [category C] [abelian C]
    {P : C} {Q : C} (f : P ⟶ Q) : epi (images.factor_thru_image f) :=
  (fun (this : epi (non_preadditive_abelian.factor_thru_image f)) => this)
    (non_preadditive_abelian.factor_thru_image.category_theory.epi f)

theorem image_ι_comp_eq_zero {C : Type u} [category C] [abelian C] {P : C} {Q : C} {f : P ⟶ Q}
    {R : C} {g : Q ⟶ R} (h : f ≫ g = 0) : image.ι f ≫ g = 0 :=
  sorry

protected instance mono_factor_thru_image {C : Type u} [category C] [abelian C] {P : C} {Q : C}
    (f : P ⟶ Q) [mono f] : mono (images.factor_thru_image f) :=
  mono_of_mono_fac (image.fac f)

protected instance is_iso_factor_thru_image {C : Type u} [category C] [abelian C] {P : C} {Q : C}
    (f : P ⟶ Q) [mono f] : is_iso (images.factor_thru_image f) :=
  is_iso_of_mono_of_epi (images.factor_thru_image f)

/-- Factoring through the image is a strong epi-mono factorisation. -/
@[simp] theorem image_strong_epi_mono_factorisation_to_mono_factorisation_m {C : Type u}
    [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) :
    limits.mono_factorisation.m
          (limits.strong_epi_mono_factorisation.to_mono_factorisation
            (image_strong_epi_mono_factorisation f)) =
        image.ι f :=
  Eq.refl
    (limits.mono_factorisation.m
      (limits.strong_epi_mono_factorisation.to_mono_factorisation
        (image_strong_epi_mono_factorisation f)))

end images

namespace coimages

/-- The cokernel of the kernel of `f` is called the coimage of `f`. -/
protected def coimage {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) : C :=
  limits.cokernel (limits.kernel.ι f)

/-- The projection onto the coimage. -/
protected def coimage.π {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) :
    P ⟶ coimages.coimage f :=
  limits.cokernel.π (limits.kernel.ι f)

/-- There is a canonical monomorphism `i : coimage f ⟶ Q`. -/
protected def factor_thru_coimage {C : Type u} [category C] [abelian C] {P : C} {Q : C}
    (f : P ⟶ Q) : coimages.coimage f ⟶ Q :=
  limits.cokernel.desc (limits.kernel.ι f) f sorry

/-- `f` factors through its coimage via the canonical morphism `p`. -/
protected theorem coimage.fac {C : Type u} [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) :
    coimage.π f ≫ coimages.factor_thru_coimage f = f :=
  limits.cokernel.π_desc (limits.kernel.ι f) f (factor_thru_coimage._proof_1 f)

/-- The canonical morphism `i : coimage f ⟶ Q` is a monomorphism -/
protected instance factor_thru_coimage.category_theory.mono {C : Type u} [category C] [abelian C]
    {P : C} {Q : C} (f : P ⟶ Q) : mono (coimages.factor_thru_coimage f) :=
  (fun (this : mono (non_preadditive_abelian.factor_thru_coimage f)) => this)
    (non_preadditive_abelian.factor_thru_coimage.category_theory.mono f)

protected instance epi_factor_thru_coimage {C : Type u} [category C] [abelian C] {P : C} {Q : C}
    (f : P ⟶ Q) [epi f] : epi (coimages.factor_thru_coimage f) :=
  epi_of_epi_fac (coimage.fac f)

protected instance is_iso_factor_thru_coimage {C : Type u} [category C] [abelian C] {P : C} {Q : C}
    (f : P ⟶ Q) [epi f] : is_iso (coimages.factor_thru_coimage f) :=
  is_iso_of_mono_of_epi (coimages.factor_thru_coimage f)

/-- Factoring through the coimage is a strong epi-mono factorisation. -/
@[simp] theorem coimage_strong_epi_mono_factorisation_to_mono_factorisation_m {C : Type u}
    [category C] [abelian C] {P : C} {Q : C} (f : P ⟶ Q) :
    limits.mono_factorisation.m
          (limits.strong_epi_mono_factorisation.to_mono_factorisation
            (coimage_strong_epi_mono_factorisation f)) =
        coimages.factor_thru_coimage f :=
  Eq.refl
    (limits.mono_factorisation.m
      (limits.strong_epi_mono_factorisation.to_mono_factorisation
        (coimage_strong_epi_mono_factorisation f)))

end coimages

/-- An abelian category has strong epi-mono factorisations. -/
protected instance category_theory.limits.has_strong_epi_mono_factorisations {C : Type u}
    [category C] [abelian C] : limits.has_strong_epi_mono_factorisations C :=
  limits.has_strong_epi_mono_factorisations.mk
    fun (X Y : C) (f : X ⟶ Y) => images.image_strong_epi_mono_factorisation f

/- In particular, this means that it has well-behaved images. -/

/-- There is a canonical isomorphism between the coimage and the image of a morphism. -/
def coimage_iso_image {C : Type u} [category C] [abelian C] {X : C} {Y : C} (f : X ⟶ Y) :
    coimages.coimage f ≅ images.image f :=
  limits.is_image.iso_ext
    (limits.strong_epi_mono_factorisation.to_mono_is_image
      (coimages.coimage_strong_epi_mono_factorisation f))
    (limits.strong_epi_mono_factorisation.to_mono_is_image
      (images.image_strong_epi_mono_factorisation f))

/-- There is a canonical isomorphism between the abelian image and the categorical image of a
    morphism. -/
def image_iso_image {C : Type u} [category C] [abelian C] {X : C} {Y : C} (f : X ⟶ Y) :
    images.image f ≅ limits.image f :=
  limits.is_image.iso_ext
    (limits.strong_epi_mono_factorisation.to_mono_is_image
      (images.image_strong_epi_mono_factorisation f))
    (limits.image.is_image f)

/-- There is a canonical isomorphism between the abelian coimage and the categorical image of a
    morphism. -/
def coimage_iso_image' {C : Type u} [category C] [abelian C] {X : C} {Y : C} (f : X ⟶ Y) :
    coimages.coimage f ≅ limits.image f :=
  limits.is_image.iso_ext
    (limits.strong_epi_mono_factorisation.to_mono_is_image
      (coimages.coimage_strong_epi_mono_factorisation f))
    (limits.image.is_image f)

theorem full_image_factorisation {C : Type u} [category C] [abelian C] {X : C} {Y : C} (f : X ⟶ Y) :
    coimages.coimage.π f ≫ iso.hom (coimage_iso_image f) ≫ images.image.ι f = f :=
  sorry

/-- In an abelian category, an epi is the cokernel of its kernel. More precisely:
    If `f` is an epimorphism and `s` is some limit kernel cone on `f`, then `f` is a cokernel
    of `fork.ι s`. -/
def epi_is_cokernel_of_kernel {C : Type u} [category C] [abelian C] {X : C} {Y : C} {f : X ⟶ Y}
    [epi f] (s : limits.fork f 0) (h : limits.is_limit s) :
    limits.is_colimit (limits.cokernel_cofork.of_π f (epi_is_cokernel_of_kernel._proof_1 s)) :=
  non_preadditive_abelian.epi_is_cokernel_of_kernel s h

/-- In an abelian category, a mono is the kernel of its cokernel. More precisely:
    If `f` is a monomorphism and `s` is some colimit cokernel cocone on `f`, then `f` is a kernel
    of `cofork.π s`. -/
def mono_is_kernel_of_cokernel {C : Type u} [category C] [abelian C] {X : C} {Y : C} {f : X ⟶ Y}
    [mono f] (s : limits.cofork f 0) (h : limits.is_colimit s) :
    limits.is_limit (limits.kernel_fork.of_ι f (mono_is_kernel_of_cokernel._proof_1 s)) :=
  non_preadditive_abelian.mono_is_kernel_of_cokernel s h

/-- Any abelian category has pullbacks -/
theorem has_pullbacks {C : Type u} [category C] [abelian C] : limits.has_pullbacks C :=
  limits.has_pullbacks_of_has_binary_products_of_has_equalizers C

/-- Any abelian category has pushouts -/
theorem has_pushouts {C : Type u} [category C] [abelian C] : limits.has_pushouts C :=
  limits.has_pushouts_of_has_binary_coproducts_of_has_coequalizers C

namespace pullback_to_biproduct_is_kernel

/-! This section contains a slightly technical result about pullbacks and biproducts.
    We will need it in the proof that the pullback of an epimorphism is an epimorpism. -/

/-- The canonical map `pullback f g ⟶ X ⊞ Y` -/
def pullback_to_biproduct {C : Type u} [category C] [abelian C] [limits.has_pullbacks C] {X : C}
    {Y : C} {Z : C} (f : X ⟶ Z) (g : Y ⟶ Z) : limits.pullback f g ⟶ X ⊞ Y :=
  limits.biprod.lift limits.pullback.fst limits.pullback.snd

/-- The canonical map `pullback f g ⟶ X ⊞ Y` induces a kernel cone on the map
    `biproduct X Y ⟶ Z` induced by `f` and `g`. A slightly more intuitive way to think of
    this may be that it induces an equalizer fork on the maps induced by `(f, 0)` and
    `(0, g)`. -/
def pullback_to_biproduct_fork {C : Type u} [category C] [abelian C] [limits.has_pullbacks C]
    {X : C} {Y : C} {Z : C} (f : X ⟶ Z) (g : Y ⟶ Z) :
    limits.kernel_fork (limits.biprod.desc f (-g)) :=
  limits.kernel_fork.of_ι (pullback_to_biproduct f g) sorry

/-- The canonical map `pullback f g ⟶ X ⊞ Y` is a kernel of the map induced by
    `(f, -g)`. -/
def is_limit_pullback_to_biproduct {C : Type u} [category C] [abelian C] [limits.has_pullbacks C]
    {X : C} {Y : C} {Z : C} (f : X ⟶ Z) (g : Y ⟶ Z) :
    limits.is_limit (pullback_to_biproduct_fork f g) :=
  limits.fork.is_limit.mk (pullback_to_biproduct_fork f g)
    (fun (s : limits.fork (limits.biprod.desc f (-g)) 0) =>
      limits.pullback.lift (limits.fork.ι s ≫ limits.biprod.fst)
        (limits.fork.ι s ≫ limits.biprod.snd) sorry)
    sorry sorry

end pullback_to_biproduct_is_kernel

namespace biproduct_to_pushout_is_cokernel

/-- The canonical map `Y ⊞ Z ⟶ pushout f g` -/
def biproduct_to_pushout {C : Type u} [category C] [abelian C] [limits.has_pushouts C] {X : C}
    {Y : C} {Z : C} (f : X ⟶ Y) (g : X ⟶ Z) : Y ⊞ Z ⟶ limits.pushout f g :=
  limits.biprod.desc limits.pushout.inl limits.pushout.inr

/-- The canonical map `Y ⊞ Z ⟶ pushout f g` induces a cokernel cofork on the map
    `X ⟶ Y ⊞ Z` induced by `f` and `-g`. -/
def biproduct_to_pushout_cofork {C : Type u} [category C] [abelian C] [limits.has_pushouts C]
    {X : C} {Y : C} {Z : C} (f : X ⟶ Y) (g : X ⟶ Z) :
    limits.cokernel_cofork (limits.biprod.lift f (-g)) :=
  limits.cokernel_cofork.of_π (biproduct_to_pushout f g) sorry

/-- The cofork induced by the canonical map `Y ⊞ Z ⟶ pushout f g` is in fact a colimit cokernel
    cofork. -/
def is_colimit_biproduct_to_pushout {C : Type u} [category C] [abelian C] [limits.has_pushouts C]
    {X : C} {Y : C} {Z : C} (f : X ⟶ Y) (g : X ⟶ Z) :
    limits.is_colimit (biproduct_to_pushout_cofork f g) :=
  limits.cofork.is_colimit.mk (biproduct_to_pushout_cofork f g)
    (fun (s : limits.cofork (limits.biprod.lift f (-g)) 0) =>
      limits.pushout.desc (limits.biprod.inl ≫ limits.cofork.π s)
        (limits.biprod.inr ≫ limits.cofork.π s) sorry)
    sorry sorry

end biproduct_to_pushout_is_cokernel

/-- In an abelian category, the pullback of an epimorphism is an epimorphism.
    Proof from [aluffi2016, IX.2.3], cf. [borceux-vol2, 1.7.6] -/
-- It will suffice to consider some morphism e : Y ⟶ R such that

protected instance epi_pullback_of_epi_f {C : Type u} [category C] [abelian C]
    [limits.has_pullbacks C] {X : C} {Y : C} {Z : C} (f : X ⟶ Z) (g : Y ⟶ Z) [epi f] :
    epi limits.pullback.snd :=
  sorry

-- pullback.snd ≫ e = 0 and show that e = 0.

/-- In an abelian category, the pullback of an epimorphism is an epimorphism. -/
-- It will suffice to consider some morphism e : X ⟶ R such that

protected instance epi_pullback_of_epi_g {C : Type u} [category C] [abelian C]
    [limits.has_pullbacks C] {X : C} {Y : C} {Z : C} (f : X ⟶ Z) (g : Y ⟶ Z) [epi g] :
    epi limits.pullback.fst :=
  sorry

-- pullback.fst ≫ e = 0 and show that e = 0.

protected instance mono_pushout_of_mono_f {C : Type u} [category C] [abelian C]
    [limits.has_pushouts C] {X : C} {Y : C} {Z : C} (f : X ⟶ Y) (g : X ⟶ Z) [mono f] :
    mono limits.pushout.inr :=
  sorry

protected instance mono_pushout_of_mono_g {C : Type u} [category C] [abelian C]
    [limits.has_pushouts C] {X : C} {Y : C} {Z : C} (f : X ⟶ Y) (g : X ⟶ Z) [mono g] :
    mono limits.pushout.inl :=
  sorry

end category_theory.abelian

namespace category_theory.non_preadditive_abelian

/-- Every non_preadditive_abelian category can be promoted to an abelian category. -/
def abelian (C : Type u) [category C] [non_preadditive_abelian C] : abelian C :=
  abelian.mk (fun (X Y : C) (f : X ⟶ Y) (_inst_2_1 : mono f) => eq.mpr sorry (normal_mono f))
    fun (X Y : C) (f : X ⟶ Y) (_inst_2_1 : epi f) => eq.mpr sorry (normal_epi f)

end Mathlib


import ring_theory.ideal.operations

section induction

namespace submodule
universes u v
variables {R : Type u} {M : Type v} {F : Type*} {G : Type*}
variables [comm_semiring R] [add_comm_monoid M] [module R M]
variables {I J : ideal R} {N P Q : submodule R M}

-- TODO : add other if needed
end submodule

end induction

section factorial
variables {A : Type*} [comm_ring A] {I : ideal A}

lemma factorial_is_unit {n : ℕ} (hn_fac : is_unit ((n-1).factorial : A))
  {m : ℕ} (hmn : m < n) : is_unit (m.factorial : A) :=
begin
  apply is_unit_of_dvd_unit _ hn_fac,
  obtain ⟨c, hc⟩ := nat.factorial_dvd_factorial (nat.le_pred_of_lt hmn),
  use (c : A),
  rw [← nat.cast_mul, hc],
end

lemma factorial.is_unit [algebra ℚ A] (n : ℕ) : is_unit (n.factorial : A) :=
begin
  rw [← map_nat_cast (algebra_map ℚ A)],
  apply is_unit.map,
  exact is_unit_iff_ne_zero.mpr (nat.cast_ne_zero.mpr (nat.factorial_ne_zero n)),
end

end factorial

section inverse

namespace ring
lemma inverse_pow_mul_eq_iff_eq_mul {M₀ : Type*} [comm_monoid_with_zero M₀] {a : M₀}
  (b c : M₀) (ha : is_unit a) {k : ℕ} : (ring.inverse a)^k * b = c ↔ b = a^k * c :=
by rw [ring.inverse_pow, ring.inverse_mul_eq_iff_eq_mul _ _ _ (is_unit.pow _ ha)]
end ring

end inverse

lemma ideal.mem_pow_eq_zero {A : Type*} [comm_ring A] {I : ideal A} (n m : ℕ) (hnI : I^n = 0)
  (hmn : n ≤ m) {x : A} (hx : x ∈ I) : x ^ m = 0 :=
begin
  have hxn : x^n = 0,
  { rw [ideal.zero_eq_bot] at hnI,
    rw [← ideal.mem_bot, ← hnI],
    exact ideal.pow_mem_pow hx n },
  obtain ⟨c, hc⟩ := nat.exists_eq_add_of_le hmn,
  rw [hc, pow_add, hxn, zero_mul]
end


/-
Copyright (c) 2021 Yaël Dillies, Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies, Bhavik Mehta
-/
import combinatorics.simplicial_complex.convex_join
import combinatorics.simplicial_complex.exposed

open set
open_locale classical big_operators

variables {E : Type*} [normed_group E] [normed_space ℝ E] {x : E} {X Y : finset E} {C : set E}

/-! ### Polyhedrons -/

/-- A polyhedron is an intersection of finitely many halfspaces. -/
noncomputable structure polyhedron (E : Type*) [normed_group E] [normed_space ℝ E] :=
(carrier : set E)
(hcarrier : ∃ Hrepr : finset ((E →L[ℝ] ℝ) × ℝ), carrier = {x | ∀ l ∈ Hrepr, (l.2 : ℝ) ≤ l.1 x})

namespace polyhedron

instance : has_coe (polyhedron E) (set E) := { coe := λ P, P.carrier }

instance : has_bot (polyhedron E) :=
{ bot := { carrier := ∅, hcarrier := ⟨{(0, 1)}, (subset_empty_iff.1 (begin
    rintro x hx,
    have : (1 : ℝ) ≤ 0 := hx (0, 1) (finset.mem_singleton_self _),
    linarith,
  end)).symm⟩ } }

@[ext] protected lemma ext {P Q : polyhedron E} (h : (P : set E) = Q) : P = Q :=
begin
  sorry
end

noncomputable def Hrepr (P : polyhedron E) : finset ((E →L[ℝ] ℝ) × ℝ) :=
classical.some P.hcarrier

lemma eq_Hrepr (P : polyhedron E) : (P : set E) = {x | ∀ l ∈ P.Hrepr, (l.2 : ℝ) ≤ l.1 x} :=
classical.some_spec P.hcarrier

lemma eq_Inter_halfspaces (P : polyhedron E) :
  (P : set E) = ⋂ l ∈ P.Hrepr, {x | (l.2 : ℝ) ≤ l.1 x} :=
begin
  rw P.eq_Hrepr,
  ext,
  simp only [mem_Inter, mem_set_of_eq],
end

lemma convex (P : polyhedron E) : convex (P : set E) :=
begin
  rw P.eq_Inter_halfspaces,
  exact convex_Inter (λ l, convex_Inter (λ hl, convex_halfspace_ge l.1.is_linear l.2)),
end

protected noncomputable def std_simplex (ι : Type*) [fintype ι] : polyhedron (ι → ℝ) :=
{ carrier := std_simplex ι,
  hcarrier := begin
    let f : ι → ((ι → ℝ) →L[ℝ] ℝ) × ℝ := λ i, ⟨{ to_fun := λ x, x i,
      map_add' := λ x y, rfl,
      map_smul' := λ c x, rfl }, 0⟩,
    let f₁ : (ι → ℝ) →L[ℝ] ℝ := { to_fun := λ x, ∑ (i : ι), x i,
      map_add' := λ x y, sorry,
      map_smul' := λ c x, sorry },
    use (finset.image f finset.univ) ∪ {⟨f₁, 1⟩} ∪ {⟨-f₁, -1⟩},
    rw std_simplex_eq_inter,
    ext,
    split,
    { rintro ⟨hx, hx₁⟩ l hl,
      simp at hl,
      obtain ⟨i, hl⟩ | rfl | rfl := hl,
      { rw ←hl,
        simp only [mem_Inter] at hx,
        exact hx i },
      { exact ge_of_eq hx₁ },
      simp only [neg_le_neg_iff, linear_map.coe_mk, continuous_linear_map.coe_mk',
        continuous_linear_map.neg_apply],
      exact le_of_eq hx₁ },
    rintro hx,
    apply mem_inter,
    { simp only [mem_Inter],
      intro i,
      apply hx (f i),
      apply finset.mem_union_left,
      apply finset.mem_union_left,
      apply finset.mem_image_of_mem,
      exact finset.mem_univ i },
    apply le_antisymm,
    { rw ←neg_le_neg_iff,
      apply hx ⟨-f₁, -1⟩,
      apply finset.mem_union_right,
      exact finset.mem_singleton_self _ },
    apply hx ⟨f₁, 1⟩,
    apply finset.mem_union_left,
    apply finset.mem_union_right,
    exact finset.mem_singleton_self _,
  end }

protected lemma std_simplex_eq (ι : Type*) [fintype ι] :
  (polyhedron.std_simplex ι : set (ι → ℝ)) = std_simplex ι :=
rfl

def faces (P : polyhedron E) : set (polyhedron E) :=
{Q | (Q : set E).nonempty → ∃ s ⊆ P.Hrepr, (Q : set E) = {x ∈ P | ∀ l ∈ s, (l.1 x : ℝ) ≤ l.2}}
--{Q | (Q : set E).nonempty → ∃ l : (E →L[ℝ] ℝ) × ℝ, Q.Hrepr = insert l P.Hrepr ∧
  --(Q : set E) = {x ∈ P | ∀ y ∈ (P : set E), l.1 y ≤ l.1 x}}

lemma is_exposed_of_mem_faces {P Q : polyhedron E} (hQ : Q ∈ P.faces) : is_exposed (P : set E) Q :=
begin
  sorry
  /-
  intro hQnemp,
  obtain ⟨s, hs, hQcarr⟩ := hQ hQnemp,
  obtain rfl | hsnemp := s.eq_empty_or_nonempty,
  /-{ use 0,
    rw hQcarr,
    apply congr_arg (has_inter.inter ↑P),
    ext, simp,
    exact λ _ _, le_rfl },-/
    sorry,
  refine ⟨-((finset.image prod.fst s).sum id), _⟩,
  rw [hQcarr, P.eq_Hrepr],
  refine subset.antisymm (λ x hx, ⟨hx.1, λ y hy, _⟩) (λ x hx, ⟨hx.1, λ la hla, _⟩),
  { simp only [neg_le_neg_iff, finset.sum_apply, id.def, continuous_linear_map.coe_sum',
    continuous_linear_map.neg_apply],
    apply finset.sum_le_sum,
    rintro l hl,
    rw finset.mem_image at hl,
    obtain ⟨la, hla, hl⟩ := hl,
    rw ←hl,
    exact (hx.2 la hla).trans (hy la (hs hla)) },
  have := hx.1 la (hs hla),
  dsimp at hx,
  -/
end

lemma subset_of_mem_faces {P Q : polyhedron E} (hQ : Q ∈ P.faces) : (Q : set E) ⊆ P :=
(is_exposed_of_mem_faces hQ).subset

lemma bot_mem_faces (P : polyhedron E) : ⊥ ∈ P.faces :=
begin
  intro h,
  exfalso,
  exact empty_not_nonempty h,
end

lemma self_mem_faces (P : polyhedron E) : P ∈ P.faces :=
begin
  intro h,
  refine ⟨∅, empty_subset _, (inter_eq_left_iff_subset.2 (λ x _, _)).symm⟩,
  rintro l hl,
  exfalso,
  exact hl,
end

lemma faces_finite (P : polyhedron E) : finite P.faces := sorry

noncomputable instance face_lattice {P : polyhedron E} : complete_lattice P.faces :=
sorry
/-
{ le := λ ⟨X, hX⟩ ⟨Y, hY⟩, X ∈ Y.faces,
  le_refl := λ ⟨X, hX⟩, X.self_mem_faces,
  le_trans := λ ⟨X, hX⟩ ⟨Y, hY⟩ ⟨Z, hZ⟩ hXY hYZ hXnemp, begin
    obtain ⟨sX, hX⟩ := hXY hXnemp,
    sorry
  end,
  le_antisymm := λ ⟨X, hX⟩ ⟨Y, hY⟩ hXY hYX, polyhedron.ext (subset.antisymm (subset_of_mem_faces hXY)
    (subset_of_mem_faces hYX)),

  sup := λ ⟨X, hX⟩, if hXnemp : (X : set E) = ∅ then id else (λ ⟨Y, hY⟩, if hYnemp : (Y : set E) = ∅
    then ⟨X, hX⟩ else begin
      rw [←ne.def, ne_empty_iff_nonempty] at hXnemp hYnemp,
      let sX := classical.some (hX hXnemp),
      have hsX := classical.some (classical.some_spec (hX hXnemp)),
      have hX := classical.some_spec (classical.some_spec (hX hXnemp)),
      let sY := classical.some (hY hYnemp),
      have hsY := classical.some (classical.some_spec (hY hYnemp)),
      have hY := classical.some_spec (classical.some_spec (hY hYnemp)),
      --refine ⟨sX ∩ sY, _⟩,
    --classical,
  sorry
  end⟩,
  le_sup_left := _,
  le_sup_right := _,≠
  sup_le := _,

  inf := λ ⟨X, hX⟩, if hXnemp : (X : set E) = ∅ then id else (λ ⟨Y, hY⟩, if hYnemp : (Y : set E) = ∅
    then ⟨X, hX⟩ else begin
      rw [←ne.def, ne_empty_iff_nonempty] at hXnemp hYnemp,
      let sX := classical.some (hX hXnemp),
      have hsX := classical.some (classical.some_spec (hX hXnemp)),
      have hX := classical.some_spec (classical.some_spec (hX hXnemp)),
      let sY := classical.some (hY hYnemp),
      have hsY := classical.some (classical.some_spec (hY hYnemp)),
      have hY := classical.some_spec (classical.some_spec (hY hYnemp)),
      refine ⟨X ∩ Y, _⟩,
    --classical,
  sorry
  end⟩,
  inf_le_left := _,
  inf_le_right := _,
  le_inf := _,

  top := _,
  le_top := _,

  bot := ⟨⊥, P.bot_mem_faces⟩,
  bot_le := λ ⟨X, hX⟩, X.bot_mem_faces,

  Sup := _,
  le_Sup := _,
  Sup_le := _,

  Inf := _,
  Inf_le := _,
  le_Inf := _ }
-/
end polyhedron

def is_exposed.to_face {P : polyhedron E} {A : set E} (hA : is_exposed (P : set E) A) :
  polyhedron E :=
{ carrier := A,
  hcarrier := begin
    obtain rfl | hAnemp := A.eq_empty_or_nonempty,
    { exact (⊥ : polyhedron E).hcarrier },
    obtain ⟨l, rfl⟩ := hA hAnemp,
    sorry
  end }

lemma is_exposed.to_face_eq {P : polyhedron E} {A : set E} (hA : is_exposed (P : set E) A) :
  (hA.to_face : set E) = A :=
rfl

lemma is_exposed.to_face_mem_face {P : polyhedron E} {A : set E} (hA : is_exposed (P : set E) A) :
  hA.to_face ∈ P.faces :=
begin
  sorry
end

namespace continuous_linear_map
variables {F : Type*} [normed_group F] [normed_space ℝ F] (L : E →L[ℝ] F)

def image_polyhedron (P : polyhedron E) : polyhedron F :=
{ carrier := L '' P,
  hcarrier := begin
    let f : (E →L[ℝ] ℝ) × ℝ → (F →L[ℝ] ℝ) × ℝ := λ l, ⟨begin
      sorry
      --have := l.1,
      --have := continuous_linear_map.comp _ L,
      --have : F → set E := λ x, this ⁻¹' {x},
    end, l.2⟩,
    use finset.image f P.Hrepr,
    sorry
  end }

lemma image_polyhedron_eq (P : polyhedron E) : (L.image_polyhedron P : set F) = L '' P := rfl

def preimage_polyhedron (P : polyhedron F) : polyhedron E :=
{ carrier := L ⁻¹' P,
  hcarrier := begin
    let f : (F →L[ℝ] ℝ) × ℝ → (E →L[ℝ] ℝ) × ℝ := λ l, ⟨l.1.comp L, l.2⟩,
    use finset.image f P.Hrepr,
    ext,
    split,
    {
      rintro hx l hl,
      rw mem_preimage at hx,
      rw finset.mem_image at hl,
      obtain ⟨l', hl', rfl⟩ := hl,
      sorry
    },
    sorry
  end }

end continuous_linear_map

/---/
def lattice_polyhedrons : semilattice_inf_top (polyhedron E) :=
{ le := λ X Y, (X : set E) ⊆ Y,
  le_refl := λ X, subset.refl X,
  le_trans := λ X Y Z, subset.trans,
  le_antisymm := λ X Y hXY hYX, polyhedron.ext (subset.antisymm (hXY : _ ⊆ _) (hYX : _ ⊆ _)),

  inf := λ X Y, { carrier := X ∩ Y,
  hcarrier := begin
    use X.Hrepr ∪ Y.Hrepr,
    rw [X.eq_Hrepr, Y.eq_Hrepr],
    apply subset.antisymm,
    { rintro x ⟨hxX, hxY⟩ l hl,
      cases finset.mem_union.1 hl,
      { exact hxX l h },
      exact hxY l h },
    rintro x hx,
    exact ⟨λ l hl, hx l (finset.mem_union_left _ hl), λ l hl, hx l (finset.mem_union_right _ hl)⟩,
  end },
  inf_le_left := λ X Y, inter_subset_left X Y,
  inf_le_right := λ X Y, inter_subset_right X Y,
  le_inf := λ X Y Z, subset_inter,

  /-bot := { carrier := ∅,
    hcarrier := begin
      refine ⟨{(0, -1)}, (subset_empty_iff.1 (λ x hx, _)).symm⟩,
      have : (0 : ℝ) ≤ -1 := hx (0, -1) (finset.mem_singleton_self _),
      linarith,
    end },
  bot_le := λ X, empty_subset X,-/

  top := { carrier := univ,
    hcarrier := begin
      refine ⟨∅, (eq_univ_of_forall (λ x, _)).symm⟩,
      rintro l hl,
      exfalso,
      exact hl,
    end },
  le_top := λ X, subset_univ X }

variables [lattice (polyhedron E)]

lemma faces_mono {P Q : polyhedron E} (hPQ : P ≤ Q) : P.faces ⊆ Q.faces := sorry

open polyhedron

def face_order_polyhedrons : order_bot (polyhedron E) :=
{ le := λ X Y, X ∈ Y.faces,
  le_refl := λ X, X.self_mem_faces,
  le_trans := λ X Y Z hXY hYZ hXnemp, begin
    obtain ⟨sX, hX⟩ := hXY hXnemp,
    sorry
  end,
  le_antisymm := λ X Y hXY hYX, polyhedron.ext (subset.antisymm (subset_of_mem_faces hXY)
    (subset_of_mem_faces hYX)),

  bot := ⊥,
  bot_le := λ X, X.bot_mem_faces }

/-- The faces of a polyhedron form a bounded and graded lattice. The grading function is the
dimensican of the face. -/
def face_lattice_polyhedron (P : polyhedron E) : bounded_lattice P.faces :=
sorry

/-
{ le := λ ⟨X, hX⟩ ⟨Y, hY⟩, X ∈ Y.faces,
  le_refl := λ ⟨X, hX⟩, X.self_mem_faces,
  le_trans := λ ⟨X, hX⟩ ⟨Y, hY⟩ ⟨Z, hZ⟩ hXY hYZ hXnemp, begin
    obtain ⟨sX, hX⟩ := hXY hXnemp,
    sorry
  end,
  le_antisymm := λ ⟨X, hX⟩ ⟨Y, hY⟩ hXY hYX, polyhedron.ext (subset.antisymm (subset_of_mem_faces hXY)
    (subset_of_mem_faces hYX)),

  inf := λ X Y, { carrier := X ∩ Y,
  hcarrier := begin
    use X.Hrepr ∪ Y.Hrepr,
    rw [X.eq_Hrepr, Y.eq_Hrepr],
    apply subset.antisymm,
    { rintro x ⟨hxX, hxY⟩ l hl,
      cases finset.mem_union.1 hl,
      { exact hxX l h },
      exact hxY l h },
    rintro x hx,
    exact ⟨λ l hl, hx l (finset.mem_union_left _ hl), λ l hl, hx l (finset.mem_union_right _ hl)⟩,
  end },
  inf_le_left := λ ⟨X, hX⟩ ⟨Y, hY⟩, inter_subset_left X Y,
  inf_le_right := λ ⟨X, hX⟩ ⟨Y, hY⟩, inter_subset_right X Y,
  le_inf := λ ⟨X, hX⟩ ⟨Y, hY⟩ Z, subset_inter,

  bot := ⟨⊥, P.bot_mem_faces⟩,
  bot_le := λ ⟨X, hX⟩, X.bot_mem_faces,

  top := ⟨P, P.self_mem_faces⟩,
  le_top := λ ⟨X, hX⟩, hX }
-/

/-! ### Polytopes -/

/-- A polytope is the convex hull of a finite number of points. -/
structure polytope (E : Type*) [normed_group E] [normed_space ℝ E] :=
(carrier : set E)
(hcarrier : ∃ Vrepr : finset E, carrier = sorry) -- -convex_hull Vrepr)

namespace polytope

instance : has_coe (polytope E) (set E) := { coe := λ P, P.carrier }

instance : has_bot (polytope E) :=
{ bot := { carrier := ∅, hcarrier := ⟨∅,sorry ⟩}}-- convex_hull_empty.symm⟩ } }

@[ext] protected lemma ext {P Q : polytope E} (h : (P : set E) = Q) : P = Q :=
begin
  sorry
end

noncomputable def Vrepr (P : polytope E) : finset E :=
classical.some P.hcarrier

--lemma eq_convex_hull_Vrepr (P : polytope E) : (P : set E) = convex_hull P.Vrepr :=
--classical.some_spec P.hcarrier

lemma convex (P : polytope E) : convex (P : set E) :=
begin
  sorry,
  --rw P.eq_convex_hull_Vrepr,
  --exact convex_convex_hull _,
end

instance lattice_polytopes : lattice (polytope E) :=
{ le := λ X Y, (X : set E) ⊆ Y,
  le_refl := λ X, subset.refl X,
  le_trans := λ X Y Z, subset.trans,
  le_antisymm := λ X Y hXY hYX, polytope.ext (subset.antisymm (hXY : _ ⊆ _) (hYX : _ ⊆ _)),

  sup := λ X Y, { carrier := convex_join X Y,
    hcarrier := begin
      use X.Vrepr ∪ Y.Vrepr,
      sorry,
      --rw [X.eq_convex_hull_Vrepr, Y.eq_convex_hull_Vrepr, ←convex_hull_union],
      --norm_cast,
    end },
  le_sup_left := λ X Y, subset_convex_join_left X Y,
  le_sup_right := λ X Y, subset_convex_join_right X Y,
  sup_le := λ X Y Z hXZ hYZ, convex_join_min hXZ hYZ Z.convex,

  inf := λ X Y, { carrier := X ∩ Y,
    hcarrier := begin
      sorry,
    end },
  inf_le_left := λ X Y, inter_subset_left X Y,
  inf_le_right := λ X Y, inter_subset_right X Y,
  le_inf := λ X Y Z, subset_inter,

  --bot := ∅,
  --bot_le := λ X, begin sorry end
  }

protected noncomputable def std_simplex (ι : Type*) [fintype ι] : polytope (ι → ℝ) :=
{ carrier := std_simplex ι,
  hcarrier := sorry, --⟨finset.image (λ (i j : ι), ite (i = j) 1 0) finset.univ,
  --by rw [←convex_hull_basis_eq_std_simplex, finset.coe_image, finset.coe_univ, image_univ]⟩ }
}
end polytope

namespace linear_map
variables {F : Type*} [normed_group F] [normed_space ℝ F] (l : E →ₗ[ℝ] F)

def image_polytope (P : polytope E) : polytope F :=
sorry
/-
{ carrier := l '' P,
  hcarrier := --⟨finset.image l P.Vrepr, by rw [P.eq_convex_hull_Vrepr, finset.coe_image,
  --  l.convex_hull_image]⟩ }
}
-/

end linear_map

lemma finset.convex_hull_eq_image {s : finset E} :
  convex_hull (s : set E) =
  (⇑(∑ x : (s : set E), (@linear_map.proj ℝ (s : set E) _ (λ i, ℝ) _ _ x).smul_right x.1)) ''
  (std_simplex (s : set E)) :=
begin
  have := (∑ x : (s : set E),
  (@linear_map.proj ℝ (s : set E) _ (λ i, ℝ) _ _ x).smul_right x.1),
  have := (∑ x : (s : set E),
  (@continuous_linear_map.proj ℝ _ (s : set E) (λ i, ℝ) _ _ _ x).smul_right x.1),
  rw set.finite.convex_hull_eq_image (finset.finite_to_set _),
  sorry
end

namespace polytope

protected def polyhedron (P : polytope E) : polyhedron E :=
{ carrier := P,
  hcarrier := begin
    let Q :=
continuous_linear_map.image_polyhedron (∑ x : (P.Vrepr : set E), (@continuous_linear_map.proj ℝ _
  (P.Vrepr : set E) (λ i, ℝ) _ _ _ x).smul_right x.1) (polyhedron.std_simplex (P.Vrepr : set E)),
  use Q.Hrepr,
  -- rw [P.eq_convex_hull_Vrepr, finset.convex_hull_eq_image, ←Q.eq_Hrepr,
  --   continuous_linear_map.image_polyhedron_eq, polyhedron.std_simplex_eq],
  --have : ⇑(∑ (x : (P.Vrepr : set E)), (@linear_map.proj ℝ (P.Vrepr : set E) _ (λ i, ℝ) _ _ x).smul_right x.1) =
  --  ⇑(∑ (x : (P.Vrepr : set E)), (@continuous_linear_map.proj ℝ _ (P.Vrepr : set E) (λ i, ℝ) _ _ _ x).smul_right x.1),
  --simp,
  sorry
  end }

lemma polyhedron_eq (P : polytope E) : (P.polyhedron : set E) = P :=
rfl

end polytope


/- This defines a set of elements implemented using a bianry tree. -/
import ..utils.list
import ..utils.has_preordering

universe variable u

namespace data.containers

inductive color
| red : color
| black : color

namespace color

instance : decidable_eq color := by tactic.mk_dec_eq_instance

end color

open color

/-- A red-black tree with no constraints enforced -/
inductive rbtree (E : Type _)
| empty {} : rbtree
| bin : color → rbtree → E → rbtree → rbtree

namespace rbtree

section
parameters {E : Type _}

protected
def tree_color : rbtree E → color
| empty := black
| (bin c l x r) := c

/-- Mark a tree color as black if not already. -/
def set_black : rbtree E → rbtree E
| empty := empty
| (bin c l x r) := bin black l x r

@[simp]
def set_black_is_black (t : rbtree E) : tree_color (set_black t) = black :=
begin
  cases t; simp [set_black, rbtree.tree_color],
end

/-- Create a tree with a single element. -/
def singleton (x : E) : rbtree E := bin black empty x empty

/-- Perform an in-order traversal to return the underlying list. -/
def to_list : rbtree E → list E
| empty := []
| (bin _ l x r) := l.to_list ++ x :: r.to_list

@[simp]
theorem to_list_set_black (t : rbtree E) : (set_black t).to_list = t.to_list :=
begin
  cases t; simp [set_black, to_list],
end

-----------------------------------------------------------------------
-- all_lt

section ordered

parameters [has_preordering E]

/- Return true if keys on right spine of rbtree are less then k. -/
def all_lt : rbtree E → E → Prop
| empty high := true
| (bin _ l x r) high := has_ordering.cmp x high = ordering.lt ∧ all_lt r high

instance all_lt.decidable : ∀(t:rbtree E) (x: E), decidable (all_lt t x)
| empty y := decidable.is_true true.intro
| (bin _ l x r) y := @and.decidable _ _ (by apply_instance) (all_lt.decidable r y)

theorem all_lt_congr
: ∀(t : rbtree E) (y0 y1 : E),
  all_lt t y0
→ has_ordering.cmp y0 y1 ≤ ordering.eq
→ all_lt t y1 :=
begin
  intros t y0 y1,
  induction t,
  case empty { simp [all_lt], },
  case bin _ l x r l_ind r_ind {
    simp [all_lt],

    have h0 := has_preordering.lt_of_lt_of_lt x y0 y1,
    have h1 := has_preordering.lt_of_lt_of_eq x y0 y1,
    have h2 : ¬ (ordering.gt ≤ ordering.eq) := dec_trivial,

    revert l_ind r_ind,
    destruct has_ordering.cmp y0 y1;
    destruct has_ordering.cmp x y0;
    destruct has_ordering.cmp x y1;
    intros def0 def1 def2;
    all_goals { cc, },
  },
end

@[simp]
theorem all_lt_set_black (t : rbtree E) (y : E)
: all_lt (set_black t) y = all_lt t y :=
begin
  cases t; trivial,
end

-----------------------------------------------------------------------
-- all_gt

/- Return true if keys on left spine of rbtree are greater then k. -/
def all_gt : rbtree E → E → Prop
| empty y := true
| (bin _ l x r) y := all_gt l y ∧ has_ordering.cmp y x = ordering.lt

instance all_gt.decidable : ∀(t:rbtree E) (y: E), decidable (all_gt t y)
| empty y := decidable.is_true true.intro
| (bin _ l x r) y := @and.decidable _ _ (all_gt.decidable l y) (by apply_instance)

theorem all_gt_congr
: ∀(t : rbtree E) (y0 y1 : E),
  all_gt t y1
→ has_ordering.cmp y0 y1 ≤ ordering.eq
→ all_gt t y0 :=
begin
  intros t y0 y1,
  induction t,
  case empty { simp [all_gt], },
  case bin _ l x r l_ind r_ind {
    simp [all_gt],

    have h0 := has_preordering.lt_of_lt_of_lt y0 y1 x,
    have h1 := has_preordering.lt_of_eq_of_lt y0 y1 x,
    have h2 : ¬ (ordering.gt ≤ ordering.eq) := dec_trivial,

    revert l_ind r_ind h0 h1,
    cases has_ordering.cmp y0 y1;
    cases has_ordering.cmp y1 x;
    cases has_ordering.cmp y0 x;
    all_goals { cc, },

  },
end

-----------------------------------------------------------------------
-- is_ordered

def is_ordered : rbtree E → Prop
| empty := true
| (bin _ l x r) := is_ordered l ∧ all_lt l x ∧ all_gt r x ∧ is_ordered r

instance is_ordered.decidable : decidable_pred is_ordered
| empty := begin unfold is_ordered, apply_instance, end
| (bin _ l x r) :=
  begin
    unfold is_ordered,
    apply @and.decidable _ _ _ _, exact (is_ordered.decidable l),
    apply @and.decidable _ _ _ _, apply_instance,
    apply @and.decidable _ _ _ _, apply_instance,
    exact (is_ordered.decidable r),
  end

@[simp]
theorem is_ordered_set_black (t : rbtree E)
: is_ordered (set_black t) = is_ordered t :=
begin
  cases t; trivial,
end

end ordered

-----------------------------------------------------------------------
-- well_formed

section well_formed

/-- Returns the height of the black nodes in the tree along the left-most path.-/
def black_height : rbtree E → ℕ
| empty := 0
| (bin black l x r) := l.black_height + 1
| (bin red   l x r) := l.black_height

/--
Returns true if the tree respects the red-black rules, namely:

1. The number of black nodes along every path is the same.
2. The children of a red node must be black.

We omit the rule that the root is black.
-/
def well_formed : rbtree E → Prop
| empty := true
| (bin black l x r) := well_formed l ∧ well_formed r ∧ l.black_height = r.black_height
| (bin red l x r)
  := l.tree_color = black
   ∧ r.tree_color = black
   ∧ well_formed l ∧ well_formed r ∧ l.black_height = r.black_height

instance well_formed.decidable : decidable_pred well_formed
| empty := begin unfold well_formed, apply_instance end
| (bin black l x r) :=
  begin
    unfold well_formed,
    apply @and.decidable _ _ _ _, exact (well_formed.decidable l),
    apply @and.decidable _ _ _ _, exact (well_formed.decidable r),
    apply_instance,
  end
| (bin red l x r) :=
  begin
    unfold well_formed,
    apply @and.decidable _ _ _ _, apply_instance,
    apply @and.decidable _ _ _ _, apply_instance,
    apply @and.decidable _ _ _ _, exact (well_formed.decidable l),
    apply @and.decidable _ _ _ _, exact (well_formed.decidable r),
    apply_instance,
  end

end well_formed

-----------------------------------------------------------------------
-- lookup

section lookup
parameters [has_preordering E]
parameters (p : E → ordering) [monotonic_find p]

/-- Lookup an element that matches the ordering. -/
def lookup : rbtree E → option E
| empty := none
| (bin _ l x r) :=
  match p x with
  | ordering.lt := lookup r
  | ordering.eq := x
  | ordering.gt := lookup l
  end

theorem find_eq_is_none_of_all_gt {t : rbtree E} {y : E}
: is_ordered t
→ all_gt t y
→ p y = ordering.gt
→ list.find (eq ordering.eq ∘ p) (to_list t) = failure :=
begin
  induction t,
  case empty { intros, trivial, },
  case bin _ l x r ind_l ind_r {
    have h0 : ordering.eq ≠ ordering.gt := dec_trivial,
    have h4 : (eq ordering.eq ∘ p) x = (ordering.eq = p x) := by simp [function.comp],

    simp [is_ordered, all_gt, to_list],
    intros is_ordered_l is_ordered_r all_gt_r_x all_lt_l_x all_gt_l_y y_lt_x p_y_gt,
    have p_x_gt : p x = ordering.gt :=
       monotonic_find.gt_increases x y (by simp [has_preordering.gt_lt_symm, y_lt_x])
                                         (by simp [p_y_gt]),
    have all_gt_r_y : all_gt r y :=
       all_gt_congr r y x all_gt_r_x (begin simp [y_lt_x], end),
    simp [*, list.find_append, list.find_cons, option.failure_is_none, option.none_or_else],
  },
end

theorem find_eq_is_none_of_all_lt {t : rbtree E} {y : E}
: is_ordered t
→ all_lt t y
→ p y ≤ ordering.eq
→ list.find (eq ordering.eq ∘ p) (to_list t) = failure :=
begin
  induction t,
  case empty { intros, trivial, },
  case bin _ l x r ind_l ind_r {
    have h0 : ordering.eq ≠ ordering.lt := dec_trivial,
    have h4 : (eq ordering.eq ∘ p) x = (ordering.eq = p x) := by simp [function.comp],

    simp [is_ordered, all_lt, to_list],
    intros is_ordered_l is_ordered_r all_gt_r_x all_lt_l_x all_gt_r_y x_lt_y p_y_gt,
    have p_x_lt : p x = ordering.lt := monotonic_find.lt_decreases x y x_lt_y  (begin simp[p_y_gt] end),
    have all_lt_l_y : all_lt l y, apply all_lt_congr l x y all_lt_l_x (begin simp [x_lt_y], end),
    simp [*, list.find_append, list.find_cons, option.failure_is_none, option.or_else_none],
  },
end

theorem lookup_as_list
: ∀{t : rbtree E},
   is_ordered t
   → lookup t = t.to_list.find (eq ordering.eq ∘ p) :=
begin
  intros t,
  induction t,
  case rbtree.empty { intro is_ordered_t, refl, },
  case rbtree.bin _ l x r l_ind r_ind {
    have h0 : ordering.eq ≠ ordering.lt := dec_trivial,
    have h1 : ordering.eq ≠ ordering.gt := dec_trivial,
    have h2 : ordering.lt ≤ ordering.eq := dec_trivial,
    have h3 : ordering.eq ≤ ordering.gt := dec_trivial,
    have h4 : (eq ordering.eq ∘ p) x = (ordering.eq = p x) := by simp [function.comp],

    simp [lookup, to_list, is_ordered],
    intros is_ordered_l is_ordered_r all_gt_r_x all_lt_l_x,
    have find_l_is_none := find_eq_is_none_of_all_lt p is_ordered_l all_lt_l_x,
    have find_r_is_none := find_eq_is_none_of_all_gt p is_ordered_r all_gt_r_x,
    destruct (p x);
      intro px_def;
      simp [px_def, h1] at find_l_is_none find_r_is_none;
      simp [lookup, *, list.find_append, list.find_cons,
            option.coe_is_some,
            option.failure_is_none,
            option.none_or_else,
             option.or_else_none],
  },
end

theorem lookup_eq_of_to_list_eq
: ∀{t u : rbtree E},
   is_ordered t
   → is_ordered u
   → t.to_list = u.to_list
   → lookup t = lookup u :=
begin
  intros x y x_order y_order x_eq_y,
  simp [lookup_as_list p, *],
end

end lookup

parameters [has_preordering E]

def key_is_lt (y : E) (p : E) : Prop := has_ordering.cmp p y = ordering.lt

instance key_is_lt_decidable (y : E) : decidable_pred (key_is_lt y) :=
  begin dsimp [decidable_pred, key_is_lt], apply_instance, end

def key_is_gt (y : E) (p : E) : Prop := has_ordering.cmp y p = ordering.lt

instance key_is_gt_decidable (y : E) : decidable_pred (key_is_gt y) :=
  begin dsimp [decidable_pred, key_is_gt], apply_instance, end

theorem filter_lt_of_all_lt (t : rbtree E) (y : E)
: is_ordered t → all_lt t y → t.to_list.filter (key_is_lt y) = t.to_list :=
begin
  induction t,
  case empty { intros, refl, },
  case bin _ l x r l_ind r_ind {
    simp [is_ordered, all_lt, to_list, list.filter, key_is_lt],
    intros is_ordered_l is_ordered_r all_gt_r_x all_lt_l_x all_lt_r_y cmp_lt,
    have all_lt_ly := all_lt_congr l x y all_lt_l_x (by simp [cmp_lt]),
    simp [*],
  },
end

theorem filter_gt_of_all_lt (t : rbtree E) (y : E)
: is_ordered t → all_lt t y → t.to_list.filter (key_is_gt y) = [] :=
begin
  induction t,
  case empty { intros, refl, },
  case bin _ l x r l_ind r_ind {
    simp [is_ordered, all_lt, to_list, list.filter, key_is_gt],
    intros is_ordered_l is_ordered_r all_gt_r_x all_lt_l_x all_lt_r_y cmp_lt,
    have all_lt_ly := all_lt_congr l x y all_lt_l_x (by simp [cmp_lt]),
    have h : has_ordering.cmp y x ≠ ordering.lt, {
      rw [← has_preordering.swap_cmp x y, cmp_lt],
      trivial,
    },
    simp [*],
  },
end

 theorem filter_gt_of_all_gt (t : rbtree E) (y : E)
: is_ordered t → all_gt t y → t.to_list.filter (key_is_gt y) = t.to_list :=
begin
  induction t,
  case empty { intros, refl, },
  case bin _ l x r l_ind r_ind {
    simp [is_ordered, all_gt, to_list, list.filter, key_is_gt],
    intros is_ordered_l is_ordered_r all_gt_r_x all_lt_l_x all_lt_l_y cmp_lt,
    have all_gt_ry := all_gt_congr r y x all_gt_r_x (by simp [cmp_lt]),
    simp [*],
  },
end

theorem filter_lt_of_all_gt (t : rbtree E) (y : E)
: is_ordered t → all_gt t y →  list.filter (key_is_lt y) (to_list t) = [] :=
begin
  induction t,
  case empty { intros, refl, },
  case bin _ l x r l_ind r_ind {
    simp [is_ordered, all_gt, to_list, list.filter, key_is_lt],
    intros is_ordered_l is_ordered_r all_gt_r_x all_lt_l_x all_lt_l_y cmp_lt,
    have all_gt_ry := all_gt_congr r y x all_gt_r_x (by simp [cmp_lt]),
    have h : has_ordering.cmp x y ≠ ordering.lt, {
      rw [← has_preordering.swap_cmp y x, cmp_lt],
      trivial,
    },
    simp [*],
  },
end

end
end rbtree
end data.containers