/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro

! This file was ported from Lean 3 source module data.finset.fold
! leanprover-community/mathlib commit e04043d6bf7264a3c84bc69711dc354958ca4516
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Order.Monoid.WithTop
import Mathbin.Data.Finset.Image
import Mathbin.Data.Multiset.Fold

/-!
# The fold operation for a commutative associative operation over a finset.

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

namespace Finset

open Multiset

variable {α β γ : Type _}

/-! ### fold -/

section Fold

variable (op : β → β → β) [hc : IsCommutative β op] [ha : IsAssociative β op]

-- mathport name: op
local notation a " * " b => op a b

include hc ha

#print Finset.fold /-
/-- `fold op b f s` folds the commutative associative operation `op` over the
  `f`-image of `s`, i.e. `fold (+) b f {1,2,3} = f 1 + f 2 + f 3 + b`. -/
def fold (b : β) (f : α → β) (s : Finset α) : β :=
  (s.1.map f).fold op b
#align finset.fold Finset.fold
-/

variable {op} {f : α → β} {b : β} {s : Finset α} {a : α}

#print Finset.fold_empty /-
@[simp]
theorem fold_empty : (∅ : Finset α).fold op b f = b :=
  rfl
#align finset.fold_empty Finset.fold_empty
-/

/- warning: finset.fold_cons -> Finset.fold_cons is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {s : Finset.{u1} α} {a : α} (h : Not (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a s)), Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b f (Finset.cons.{u1} α a s h)) (op (f a) (Finset.fold.{u1, u2} α β op hc ha b f s))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {s : Finset.{u2} α} {a : α} (h : Not (Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) a s)), Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha b f (Finset.cons.{u2} α a s h)) (op (f a) (Finset.fold.{u2, u1} α β op hc ha b f s))
Case conversion may be inaccurate. Consider using '#align finset.fold_cons Finset.fold_consₓ'. -/
@[simp]
theorem fold_cons (h : a ∉ s) : (cons a s h).fold op b f = f a * s.fold op b f :=
  by
  dsimp only [fold]
  rw [cons_val, Multiset.map_cons, fold_cons_left]
#align finset.fold_cons Finset.fold_cons

/- warning: finset.fold_insert -> Finset.fold_insert is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {s : Finset.{u1} α} {a : α} [_inst_1 : DecidableEq.{succ u1} α], (Not (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) a s)) -> (Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b f (Insert.insert.{u1, u1} α (Finset.{u1} α) (Finset.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a s)) (op (f a) (Finset.fold.{u1, u2} α β op hc ha b f s)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {s : Finset.{u2} α} {a : α} [_inst_1 : DecidableEq.{succ u2} α], (Not (Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) a s)) -> (Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha b f (Insert.insert.{u2, u2} α (Finset.{u2} α) (Finset.instInsertFinset.{u2} α (fun (a : α) (b : α) => _inst_1 a b)) a s)) (op (f a) (Finset.fold.{u2, u1} α β op hc ha b f s)))
Case conversion may be inaccurate. Consider using '#align finset.fold_insert Finset.fold_insertₓ'. -/
@[simp]
theorem fold_insert [DecidableEq α] (h : a ∉ s) : (insert a s).fold op b f = f a * s.fold op b f :=
  by unfold fold <;> rw [insert_val, ndinsert_of_not_mem h, Multiset.map_cons, fold_cons_left]
#align finset.fold_insert Finset.fold_insert

#print Finset.fold_singleton /-
@[simp]
theorem fold_singleton : ({a} : Finset α).fold op b f = f a * b :=
  rfl
#align finset.fold_singleton Finset.fold_singleton
-/

/- warning: finset.fold_map -> Finset.fold_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {g : Function.Embedding.{succ u3, succ u1} γ α} {s : Finset.{u3} γ}, Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b f (Finset.map.{u3, u1} γ α g s)) (Finset.fold.{u3, u2} γ β op hc ha b (Function.comp.{succ u3, succ u1, succ u2} γ α β f (coeFn.{max 1 (succ u3) (succ u1), max (succ u3) (succ u1)} (Function.Embedding.{succ u3, succ u1} γ α) (fun (_x : Function.Embedding.{succ u3, succ u1} γ α) => γ -> α) (Function.Embedding.hasCoeToFun.{succ u3, succ u1} γ α) g)) s)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {g : Function.Embedding.{succ u3, succ u2} γ α} {s : Finset.{u3} γ}, Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha b f (Finset.map.{u3, u2} γ α g s)) (Finset.fold.{u3, u1} γ β op hc ha b (Function.comp.{succ u3, succ u2, succ u1} γ α β f (FunLike.coe.{max (succ u2) (succ u3), succ u3, succ u2} (Function.Embedding.{succ u3, succ u2} γ α) γ (fun (_x : γ) => (fun (x._@.Mathlib.Data.FunLike.Embedding._hyg.19 : γ) => α) _x) (EmbeddingLike.toFunLike.{max (succ u2) (succ u3), succ u3, succ u2} (Function.Embedding.{succ u3, succ u2} γ α) γ α (Function.instEmbeddingLikeEmbedding.{succ u3, succ u2} γ α)) g)) s)
Case conversion may be inaccurate. Consider using '#align finset.fold_map Finset.fold_mapₓ'. -/
@[simp]
theorem fold_map {g : γ ↪ α} {s : Finset γ} : (s.map g).fold op b f = s.fold op b (f ∘ g) := by
  simp only [fold, map, Multiset.map_map]
#align finset.fold_map Finset.fold_map

/- warning: finset.fold_image -> Finset.fold_image is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} [_inst_1 : DecidableEq.{succ u1} α] {g : γ -> α} {s : Finset.{u3} γ}, (forall (x : γ), (Membership.Mem.{u3, u3} γ (Finset.{u3} γ) (Finset.hasMem.{u3} γ) x s) -> (forall (y : γ), (Membership.Mem.{u3, u3} γ (Finset.{u3} γ) (Finset.hasMem.{u3} γ) y s) -> (Eq.{succ u1} α (g x) (g y)) -> (Eq.{succ u3} γ x y))) -> (Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b f (Finset.image.{u3, u1} γ α (fun (a : α) (b : α) => _inst_1 a b) g s)) (Finset.fold.{u3, u2} γ β op hc ha b (Function.comp.{succ u3, succ u1, succ u2} γ α β f g) s))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u1}} {γ : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} [_inst_1 : DecidableEq.{succ u3} α] {g : γ -> α} {s : Finset.{u2} γ}, (forall (x : γ), (Membership.mem.{u2, u2} γ (Finset.{u2} γ) (Finset.instMembershipFinset.{u2} γ) x s) -> (forall (y : γ), (Membership.mem.{u2, u2} γ (Finset.{u2} γ) (Finset.instMembershipFinset.{u2} γ) y s) -> (Eq.{succ u3} α (g x) (g y)) -> (Eq.{succ u2} γ x y))) -> (Eq.{succ u1} β (Finset.fold.{u3, u1} α β op hc ha b f (Finset.image.{u2, u3} γ α (fun (a : α) (b : α) => _inst_1 a b) g s)) (Finset.fold.{u2, u1} γ β op hc ha b (Function.comp.{succ u2, succ u3, succ u1} γ α β f g) s))
Case conversion may be inaccurate. Consider using '#align finset.fold_image Finset.fold_imageₓ'. -/
@[simp]
theorem fold_image [DecidableEq α] {g : γ → α} {s : Finset γ}
    (H : ∀ x ∈ s, ∀ y ∈ s, g x = g y → x = y) : (s.image g).fold op b f = s.fold op b (f ∘ g) := by
  simp only [fold, image_val_of_inj_on H, Multiset.map_map]
#align finset.fold_image Finset.fold_image

/- warning: finset.fold_congr -> Finset.fold_congr is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {s : Finset.{u1} α} {g : α -> β}, (forall (x : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) -> (Eq.{succ u2} β (f x) (g x))) -> (Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b f s) (Finset.fold.{u1, u2} α β op hc ha b g s))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {s : Finset.{u2} α} {g : α -> β}, (forall (x : α), (Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) x s) -> (Eq.{succ u1} β (f x) (g x))) -> (Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha b f s) (Finset.fold.{u2, u1} α β op hc ha b g s))
Case conversion may be inaccurate. Consider using '#align finset.fold_congr Finset.fold_congrₓ'. -/
@[congr]
theorem fold_congr {g : α → β} (H : ∀ x ∈ s, f x = g x) : s.fold op b f = s.fold op b g := by
  rw [fold, fold, map_congr rfl H]
#align finset.fold_congr Finset.fold_congr

#print Finset.fold_op_distrib /-
theorem fold_op_distrib {f g : α → β} {b₁ b₂ : β} :
    (s.fold op (b₁ * b₂) fun x => f x * g x) = s.fold op b₁ f * s.fold op b₂ g := by
  simp only [fold, fold_distrib]
#align finset.fold_op_distrib Finset.fold_op_distrib
-/

/- warning: finset.fold_const -> Finset.fold_const is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {b : β} {s : Finset.{u1} α} [_inst_1 : Decidable (Eq.{succ u1} (Finset.{u1} α) s (EmptyCollection.emptyCollection.{u1} (Finset.{u1} α) (Finset.hasEmptyc.{u1} α)))] (c : β), (Eq.{succ u2} β (op c (op b c)) (op b c)) -> (Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b (fun (_x : α) => c) s) (ite.{succ u2} β (Eq.{succ u1} (Finset.{u1} α) s (EmptyCollection.emptyCollection.{u1} (Finset.{u1} α) (Finset.hasEmptyc.{u1} α))) _inst_1 b (op b c)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {b : β} {s : Finset.{u2} α} [_inst_1 : Decidable (Eq.{succ u2} (Finset.{u2} α) s (EmptyCollection.emptyCollection.{u2} (Finset.{u2} α) (Finset.instEmptyCollectionFinset.{u2} α)))] (c : β), (Eq.{succ u1} β (op c (op b c)) (op b c)) -> (Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha b (fun (_x : α) => c) s) (ite.{succ u1} β (Eq.{succ u2} (Finset.{u2} α) s (EmptyCollection.emptyCollection.{u2} (Finset.{u2} α) (Finset.instEmptyCollectionFinset.{u2} α))) _inst_1 b (op b c)))
Case conversion may be inaccurate. Consider using '#align finset.fold_const Finset.fold_constₓ'. -/
theorem fold_const [Decidable (s = ∅)] (c : β) (h : op c (op b c) = op b c) :
    Finset.fold op b (fun _ => c) s = if s = ∅ then b else op b c := by
  classical
    induction' s using Finset.induction_on with x s hx IH
    · simp
    · simp only [Finset.fold_insert hx, IH, if_false, Finset.insert_ne_empty]
      split_ifs
      · rw [hc.comm]
      · exact h
#align finset.fold_const Finset.fold_const

/- warning: finset.fold_hom -> Finset.fold_hom is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {s : Finset.{u1} α} {op' : γ -> γ -> γ} [_inst_1 : IsCommutative.{u3} γ op'] [_inst_2 : IsAssociative.{u3} γ op'] {m : β -> γ}, (forall (x : β) (y : β), Eq.{succ u3} γ (m (op x y)) (op' (m x) (m y))) -> (Eq.{succ u3} γ (Finset.fold.{u1, u3} α γ op' _inst_1 _inst_2 (m b) (fun (x : α) => m (f x)) s) (m (Finset.fold.{u1, u2} α β op hc ha b f s)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u3}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {s : Finset.{u2} α} {op' : γ -> γ -> γ} [_inst_1 : IsCommutative.{u3} γ op'] [_inst_2 : IsAssociative.{u3} γ op'] {m : β -> γ}, (forall (x : β) (y : β), Eq.{succ u3} γ (m (op x y)) (op' (m x) (m y))) -> (Eq.{succ u3} γ (Finset.fold.{u2, u3} α γ op' _inst_1 _inst_2 (m b) (fun (x : α) => m (f x)) s) (m (Finset.fold.{u2, u1} α β op hc ha b f s)))
Case conversion may be inaccurate. Consider using '#align finset.fold_hom Finset.fold_homₓ'. -/
theorem fold_hom {op' : γ → γ → γ} [IsCommutative γ op'] [IsAssociative γ op'] {m : β → γ}
    (hm : ∀ x y, m (op x y) = op' (m x) (m y)) :
    (s.fold op' (m b) fun x => m (f x)) = m (s.fold op b f) := by
  rw [fold, fold, ← fold_hom op hm, Multiset.map_map]
#align finset.fold_hom Finset.fold_hom

/- warning: finset.fold_disj_union -> Finset.fold_disjUnion is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {s₁ : Finset.{u1} α} {s₂ : Finset.{u1} α} {b₁ : β} {b₂ : β} (h : Disjoint.{u1} (Finset.{u1} α) (Finset.partialOrder.{u1} α) (Finset.orderBot.{u1} α) s₁ s₂), Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha (op b₁ b₂) f (Finset.disjUnion.{u1} α s₁ s₂ h)) (op (Finset.fold.{u1, u2} α β op hc ha b₁ f s₁) (Finset.fold.{u1, u2} α β op hc ha b₂ f s₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {s₁ : Finset.{u2} α} {s₂ : Finset.{u2} α} {b₁ : β} {b₂ : β} (h : Disjoint.{u2} (Finset.{u2} α) (Finset.partialOrder.{u2} α) (Finset.instOrderBotFinsetToLEToPreorderPartialOrder.{u2} α) s₁ s₂), Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha (op b₁ b₂) f (Finset.disjUnion.{u2} α s₁ s₂ h)) (op (Finset.fold.{u2, u1} α β op hc ha b₁ f s₁) (Finset.fold.{u2, u1} α β op hc ha b₂ f s₂))
Case conversion may be inaccurate. Consider using '#align finset.fold_disj_union Finset.fold_disjUnionₓ'. -/
theorem fold_disjUnion {s₁ s₂ : Finset α} {b₁ b₂ : β} (h) :
    (s₁.disjUnion s₂ h).fold op (b₁ * b₂) f = s₁.fold op b₁ f * s₂.fold op b₂ f :=
  (congr_arg _ <| Multiset.map_add _ _ _).trans (Multiset.fold_add _ _ _ _ _)
#align finset.fold_disj_union Finset.fold_disjUnion

/- warning: finset.fold_disj_Union -> Finset.fold_disjUnionᵢ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {ι : Type.{u3}} {s : Finset.{u3} ι} {t : ι -> (Finset.{u1} α)} {b : ι -> β} {b₀ : β} (h : Set.PairwiseDisjoint.{u1, u3} (Finset.{u1} α) ι (Finset.partialOrder.{u1} α) (Finset.orderBot.{u1} α) ((fun (a : Type.{u3}) (b : Type.{u3}) [self : HasLiftT.{succ u3, succ u3} a b] => self.0) (Finset.{u3} ι) (Set.{u3} ι) (HasLiftT.mk.{succ u3, succ u3} (Finset.{u3} ι) (Set.{u3} ι) (CoeTCₓ.coe.{succ u3, succ u3} (Finset.{u3} ι) (Set.{u3} ι) (Finset.Set.hasCoeT.{u3} ι))) s) t), Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha (Finset.fold.{u3, u2} ι β op hc ha b₀ b s) f (Finset.disjUnionₓ.{u3, u1} ι α s t h)) (Finset.fold.{u3, u2} ι β op hc ha b₀ (fun (i : ι) => Finset.fold.{u1, u2} α β op hc ha (b i) f (t i)) s)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {ι : Type.{u3}} {s : Finset.{u3} ι} {t : ι -> (Finset.{u2} α)} {b : ι -> β} {b₀ : β} (h : Set.PairwiseDisjoint.{u2, u3} (Finset.{u2} α) ι (Finset.partialOrder.{u2} α) (Finset.instOrderBotFinsetToLEToPreorderPartialOrder.{u2} α) (Finset.toSet.{u3} ι s) t), Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha (Finset.fold.{u3, u1} ι β op hc ha b₀ b s) f (Finset.disjUnionᵢ.{u3, u2} ι α s t h)) (Finset.fold.{u3, u1} ι β op hc ha b₀ (fun (i : ι) => Finset.fold.{u2, u1} α β op hc ha (b i) f (t i)) s)
Case conversion may be inaccurate. Consider using '#align finset.fold_disj_Union Finset.fold_disjUnionᵢₓ'. -/
theorem fold_disjUnionᵢ {ι : Type _} {s : Finset ι} {t : ι → Finset α} {b : ι → β} {b₀ : β} (h) :
    (s.disjUnionₓ t h).fold op (s.fold op b₀ b) f = s.fold op b₀ fun i => (t i).fold op (b i) f :=
  (congr_arg _ <| Multiset.map_bind _ _ _).trans (Multiset.fold_bind _ _ _ _ _)
#align finset.fold_disj_Union Finset.fold_disjUnionᵢ

/- warning: finset.fold_union_inter -> Finset.fold_union_inter is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} [_inst_1 : DecidableEq.{succ u1} α] {s₁ : Finset.{u1} α} {s₂ : Finset.{u1} α} {b₁ : β} {b₂ : β}, Eq.{succ u2} β (op (Finset.fold.{u1, u2} α β op hc ha b₁ f (Union.union.{u1} (Finset.{u1} α) (Finset.hasUnion.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) s₁ s₂)) (Finset.fold.{u1, u2} α β op hc ha b₂ f (Inter.inter.{u1} (Finset.{u1} α) (Finset.hasInter.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) s₁ s₂))) (op (Finset.fold.{u1, u2} α β op hc ha b₂ f s₁) (Finset.fold.{u1, u2} α β op hc ha b₁ f s₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} [_inst_1 : DecidableEq.{succ u2} α] {s₁ : Finset.{u2} α} {s₂ : Finset.{u2} α} {b₁ : β} {b₂ : β}, Eq.{succ u1} β (op (Finset.fold.{u2, u1} α β op hc ha b₁ f (Union.union.{u2} (Finset.{u2} α) (Finset.instUnionFinset.{u2} α (fun (a : α) (b : α) => _inst_1 a b)) s₁ s₂)) (Finset.fold.{u2, u1} α β op hc ha b₂ f (Inter.inter.{u2} (Finset.{u2} α) (Finset.instInterFinset.{u2} α (fun (a : α) (b : α) => _inst_1 a b)) s₁ s₂))) (op (Finset.fold.{u2, u1} α β op hc ha b₂ f s₁) (Finset.fold.{u2, u1} α β op hc ha b₁ f s₂))
Case conversion may be inaccurate. Consider using '#align finset.fold_union_inter Finset.fold_union_interₓ'. -/
theorem fold_union_inter [DecidableEq α] {s₁ s₂ : Finset α} {b₁ b₂ : β} :
    ((s₁ ∪ s₂).fold op b₁ f * (s₁ ∩ s₂).fold op b₂ f) = s₁.fold op b₂ f * s₂.fold op b₁ f := by
  unfold fold <;>
    rw [← fold_add op, ← Multiset.map_add, union_val, inter_val, union_add_inter, Multiset.map_add,
      hc.comm, fold_add]
#align finset.fold_union_inter Finset.fold_union_inter

/- warning: finset.fold_insert_idem -> Finset.fold_insert_idem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {s : Finset.{u1} α} {a : α} [_inst_1 : DecidableEq.{succ u1} α] [hi : IsIdempotent.{u2} β op], Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b f (Insert.insert.{u1, u1} α (Finset.{u1} α) (Finset.hasInsert.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) a s)) (op (f a) (Finset.fold.{u1, u2} α β op hc ha b f s))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {s : Finset.{u2} α} {a : α} [_inst_1 : DecidableEq.{succ u2} α] [hi : IsIdempotent.{u1} β op], Eq.{succ u1} β (Finset.fold.{u2, u1} α β op hc ha b f (Insert.insert.{u2, u2} α (Finset.{u2} α) (Finset.instInsertFinset.{u2} α (fun (a : α) (b : α) => _inst_1 a b)) a s)) (op (f a) (Finset.fold.{u2, u1} α β op hc ha b f s))
Case conversion may be inaccurate. Consider using '#align finset.fold_insert_idem Finset.fold_insert_idemₓ'. -/
@[simp]
theorem fold_insert_idem [DecidableEq α] [hi : IsIdempotent β op] :
    (insert a s).fold op b f = f a * s.fold op b f :=
  by
  by_cases a ∈ s
  · rw [← insert_erase h]
    simp [← ha.assoc, hi.idempotent]
  · apply fold_insert h
#align finset.fold_insert_idem Finset.fold_insert_idem

/- warning: finset.fold_image_idem -> Finset.fold_image_idem is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} [_inst_1 : DecidableEq.{succ u1} α] {g : γ -> α} {s : Finset.{u3} γ} [hi : IsIdempotent.{u2} β op], Eq.{succ u2} β (Finset.fold.{u1, u2} α β op hc ha b f (Finset.image.{u3, u1} γ α (fun (a : α) (b : α) => _inst_1 a b) g s)) (Finset.fold.{u3, u2} γ β op hc ha b (Function.comp.{succ u3, succ u1, succ u2} γ α β f g) s)
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u1}} {γ : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} [_inst_1 : DecidableEq.{succ u3} α] {g : γ -> α} {s : Finset.{u2} γ} [hi : IsIdempotent.{u1} β op], Eq.{succ u1} β (Finset.fold.{u3, u1} α β op hc ha b f (Finset.image.{u2, u3} γ α (fun (a : α) (b : α) => _inst_1 a b) g s)) (Finset.fold.{u2, u1} γ β op hc ha b (Function.comp.{succ u2, succ u3, succ u1} γ α β f g) s)
Case conversion may be inaccurate. Consider using '#align finset.fold_image_idem Finset.fold_image_idemₓ'. -/
theorem fold_image_idem [DecidableEq α] {g : γ → α} {s : Finset γ} [hi : IsIdempotent β op] :
    (image g s).fold op b f = s.fold op b (f ∘ g) :=
  by
  induction' s using Finset.cons_induction with x xs hx ih
  · rw [fold_empty, image_empty, fold_empty]
  · haveI := Classical.decEq γ
    rw [fold_cons, cons_eq_insert, image_insert, fold_insert_idem, ih]
#align finset.fold_image_idem Finset.fold_image_idem

#print Finset.fold_ite' /-
/-- A stronger version of `finset.fold_ite`, but relies on
an explicit proof of idempotency on the seed element, rather
than relying on typeclass idempotency over the whole type. -/
theorem fold_ite' {g : α → β} (hb : op b b = b) (p : α → Prop) [DecidablePred p] :
    Finset.fold op b (fun i => ite (p i) (f i) (g i)) s =
      op (Finset.fold op b f (s.filterₓ p)) (Finset.fold op b g (s.filterₓ fun i => ¬p i)) :=
  by
  classical
    induction' s using Finset.induction_on with x s hx IH
    · simp [hb]
    · simp only [[anonymous], Finset.fold_insert hx]
      split_ifs with h h
      · have : x ∉ Finset.filter p s := by simp [hx]
        simp [Finset.filter_insert, h, Finset.fold_insert this, ha.assoc, IH]
      · have : x ∉ Finset.filter (fun i => ¬p i) s := by simp [hx]
        simp [Finset.filter_insert, h, Finset.fold_insert this, IH, ← ha.assoc, hc.comm]
#align finset.fold_ite' Finset.fold_ite'
-/

#print Finset.fold_ite /-
/-- A weaker version of `finset.fold_ite'`,
relying on typeclass idempotency over the whole type,
instead of solely on the seed element.
However, this is easier to use because it does not generate side goals. -/
theorem fold_ite [IsIdempotent β op] {g : α → β} (p : α → Prop) [DecidablePred p] :
    Finset.fold op b (fun i => ite (p i) (f i) (g i)) s =
      op (Finset.fold op b f (s.filterₓ p)) (Finset.fold op b g (s.filterₓ fun i => ¬p i)) :=
  fold_ite' (IsIdempotent.idempotent _) _
#align finset.fold_ite Finset.fold_ite
-/

/- warning: finset.fold_op_rel_iff_and -> Finset.fold_op_rel_iff_and is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {s : Finset.{u1} α} {r : β -> β -> Prop}, (forall {x : β} {y : β} {z : β}, Iff (r x (op y z)) (And (r x y) (r x z))) -> (forall {c : β}, Iff (r c (Finset.fold.{u1, u2} α β op hc ha b f s)) (And (r c b) (forall (x : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) -> (r c (f x)))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {s : Finset.{u2} α} {r : β -> β -> Prop}, (forall {x : β} {y : β} {z : β}, Iff (r x (op y z)) (And (r x y) (r x z))) -> (forall {c : β}, Iff (r c (Finset.fold.{u2, u1} α β op hc ha b f s)) (And (r c b) (forall (x : α), (Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) x s) -> (r c (f x)))))
Case conversion may be inaccurate. Consider using '#align finset.fold_op_rel_iff_and Finset.fold_op_rel_iff_andₓ'. -/
theorem fold_op_rel_iff_and {r : β → β → Prop} (hr : ∀ {x y z}, r x (op y z) ↔ r x y ∧ r x z)
    {c : β} : r c (s.fold op b f) ↔ r c b ∧ ∀ x ∈ s, r c (f x) := by
  classical
    apply Finset.induction_on s
    · simp
    clear s
    intro a s ha IH
    rw [Finset.fold_insert ha, hr, IH, ← and_assoc', and_comm' (r c (f a)), and_assoc']
    apply and_congr Iff.rfl
    constructor
    · rintro ⟨h₁, h₂⟩
      intro b hb
      rw [Finset.mem_insert] at hb
      rcases hb with (rfl | hb) <;> solve_by_elim
    · intro h
      constructor
      · exact h a (Finset.mem_insert_self _ _)
      · intro b hb
        apply h b
        rw [Finset.mem_insert]
        right
        exact hb
#align finset.fold_op_rel_iff_and Finset.fold_op_rel_iff_and

/- warning: finset.fold_op_rel_iff_or -> Finset.fold_op_rel_iff_or is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {op : β -> β -> β} [hc : IsCommutative.{u2} β op] [ha : IsAssociative.{u2} β op] {f : α -> β} {b : β} {s : Finset.{u1} α} {r : β -> β -> Prop}, (forall {x : β} {y : β} {z : β}, Iff (r x (op y z)) (Or (r x y) (r x z))) -> (forall {c : β}, Iff (r c (Finset.fold.{u1, u2} α β op hc ha b f s)) (Or (r c b) (Exists.{succ u1} α (fun (x : α) => Exists.{0} (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) (fun (H : Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) => r c (f x))))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {op : β -> β -> β} [hc : IsCommutative.{u1} β op] [ha : IsAssociative.{u1} β op] {f : α -> β} {b : β} {s : Finset.{u2} α} {r : β -> β -> Prop}, (forall {x : β} {y : β} {z : β}, Iff (r x (op y z)) (Or (r x y) (r x z))) -> (forall {c : β}, Iff (r c (Finset.fold.{u2, u1} α β op hc ha b f s)) (Or (r c b) (Exists.{succ u2} α (fun (x : α) => And (Membership.mem.{u2, u2} α (Finset.{u2} α) (Finset.instMembershipFinset.{u2} α) x s) (r c (f x))))))
Case conversion may be inaccurate. Consider using '#align finset.fold_op_rel_iff_or Finset.fold_op_rel_iff_orₓ'. -/
theorem fold_op_rel_iff_or {r : β → β → Prop} (hr : ∀ {x y z}, r x (op y z) ↔ r x y ∨ r x z)
    {c : β} : r c (s.fold op b f) ↔ r c b ∨ ∃ x ∈ s, r c (f x) := by
  classical
    apply Finset.induction_on s
    · simp
    clear s
    intro a s ha IH
    rw [Finset.fold_insert ha, hr, IH, ← or_assoc', or_comm' (r c (f a)), or_assoc']
    apply or_congr Iff.rfl
    constructor
    · rintro (h₁ | ⟨x, hx, h₂⟩)
      · use a
        simp [h₁]
      · refine' ⟨x, by simp [hx], h₂⟩
    · rintro ⟨x, hx, h⟩
      rw [mem_insert] at hx
      cases hx
      · left
        rwa [hx] at h
      · right
        exact ⟨x, hx, h⟩
#align finset.fold_op_rel_iff_or Finset.fold_op_rel_iff_or

omit hc ha

#print Finset.fold_union_empty_singleton /-
@[simp]
theorem fold_union_empty_singleton [DecidableEq α] (s : Finset α) :
    Finset.fold (· ∪ ·) ∅ singleton s = s :=
  by
  apply Finset.induction_on s
  · simp only [fold_empty]
  · intro a s has ih
    rw [fold_insert has, ih, insert_eq]
#align finset.fold_union_empty_singleton Finset.fold_union_empty_singleton
-/

/- warning: finset.fold_sup_bot_singleton -> Finset.fold_sup_bot_singleton is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] (s : Finset.{u1} α), Eq.{succ u1} (Finset.{u1} α) (Finset.fold.{u1, u1} α (Finset.{u1} α) (Sup.sup.{u1} (Finset.{u1} α) (SemilatticeSup.toHasSup.{u1} (Finset.{u1} α) (Lattice.toSemilatticeSup.{u1} (Finset.{u1} α) (Finset.lattice.{u1} α (fun (a : α) (b : α) => _inst_1 a b))))) (Finset.hasUnion.Union.isCommutative.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) (Finset.hasUnion.Union.isAssociative.{u1} α (fun (a : α) (b : α) => _inst_1 a b)) (Bot.bot.{u1} (Finset.{u1} α) (GeneralizedBooleanAlgebra.toHasBot.{u1} (Finset.{u1} α) (Finset.generalizedBooleanAlgebra.{u1} α (fun (a : α) (b : α) => _inst_1 a b)))) (Singleton.singleton.{u1, u1} α (Finset.{u1} α) (Finset.hasSingleton.{u1} α)) s) s
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] (s : Finset.{u1} α), Eq.{succ u1} (Finset.{u1} α) (Finset.fold.{u1, u1} α (Finset.{u1} α) (fun (x._@.Mathlib.Data.Finset.Fold._hyg.2814 : Finset.{u1} α) (x._@.Mathlib.Data.Finset.Fold._hyg.2816 : Finset.{u1} α) => Sup.sup.{u1} (Finset.{u1} α) (SemilatticeSup.toSup.{u1} (Finset.{u1} α) (Lattice.toSemilatticeSup.{u1} (Finset.{u1} α) (Finset.instLatticeFinset.{u1} α (fun (a : α) (b : α) => _inst_1 a b)))) x._@.Mathlib.Data.Finset.Fold._hyg.2814 x._@.Mathlib.Data.Finset.Fold._hyg.2816) (instIsCommutativeSupToSup.{u1} (Finset.{u1} α) (Lattice.toSemilatticeSup.{u1} (Finset.{u1} α) (Finset.instLatticeFinset.{u1} α (fun (a : α) (b : α) => _inst_1 a b)))) (instIsAssociativeSupToSup.{u1} (Finset.{u1} α) (Lattice.toSemilatticeSup.{u1} (Finset.{u1} α) (Finset.instLatticeFinset.{u1} α (fun (a : α) (b : α) => _inst_1 a b)))) (Bot.bot.{u1} (Finset.{u1} α) (GeneralizedBooleanAlgebra.toBot.{u1} (Finset.{u1} α) (Finset.instGeneralizedBooleanAlgebraFinset.{u1} α (fun (a : α) (b : α) => _inst_1 a b)))) (Singleton.singleton.{u1, u1} α (Finset.{u1} α) (Finset.instSingletonFinset.{u1} α)) s) s
Case conversion may be inaccurate. Consider using '#align finset.fold_sup_bot_singleton Finset.fold_sup_bot_singletonₓ'. -/
theorem fold_sup_bot_singleton [DecidableEq α] (s : Finset α) :
    Finset.fold (· ⊔ ·) ⊥ singleton s = s :=
  fold_union_empty_singleton s
#align finset.fold_sup_bot_singleton Finset.fold_sup_bot_singleton

section Order

variable [LinearOrder β] (c : β)

/- warning: finset.le_fold_min -> Finset.le_fold_min is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (Finset.fold.{u1, u2} α β (LinearOrder.min.{u2} β _inst_1) (inf_isCommutative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (inf_isAssociative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s)) (And (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c b) (forall (x : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) -> (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (f x))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (Finset.fold.{u1, u2} α β (Min.min.{u2} β (LinearOrder.toMin.{u2} β _inst_1)) (instIsCommutativeMinToMin.{u2} β _inst_1) (instIsAssociativeMinToMin.{u2} β _inst_1) b f s)) (And (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c b) (forall (x : α), (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) -> (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (f x))))
Case conversion may be inaccurate. Consider using '#align finset.le_fold_min Finset.le_fold_minₓ'. -/
theorem le_fold_min : c ≤ s.fold min b f ↔ c ≤ b ∧ ∀ x ∈ s, c ≤ f x :=
  fold_op_rel_iff_and fun x y z => le_min_iff
#align finset.le_fold_min Finset.le_fold_min

/- warning: finset.fold_min_le -> Finset.fold_min_le is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (Finset.fold.{u1, u2} α β (LinearOrder.min.{u2} β _inst_1) (inf_isCommutative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (inf_isAssociative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s) c) (Or (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) b c) (Exists.{succ u1} α (fun (x : α) => Exists.{0} (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) (fun (H : Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) => LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (f x) c))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (Finset.fold.{u1, u2} α β (Min.min.{u2} β (LinearOrder.toMin.{u2} β _inst_1)) (instIsCommutativeMinToMin.{u2} β _inst_1) (instIsAssociativeMinToMin.{u2} β _inst_1) b f s) c) (Or (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) b c) (Exists.{succ u1} α (fun (x : α) => And (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (f x) c))))
Case conversion may be inaccurate. Consider using '#align finset.fold_min_le Finset.fold_min_leₓ'. -/
theorem fold_min_le : s.fold min b f ≤ c ↔ b ≤ c ∨ ∃ x ∈ s, f x ≤ c :=
  by
  show _ ≥ _ ↔ _
  apply fold_op_rel_iff_or
  intro x y z
  show _ ≤ _ ↔ _
  exact min_le_iff
#align finset.fold_min_le Finset.fold_min_le

/- warning: finset.lt_fold_min -> Finset.lt_fold_min is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (Finset.fold.{u1, u2} α β (LinearOrder.min.{u2} β _inst_1) (inf_isCommutative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (inf_isAssociative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s)) (And (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c b) (forall (x : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) -> (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (f x))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (Finset.fold.{u1, u2} α β (Min.min.{u2} β (LinearOrder.toMin.{u2} β _inst_1)) (instIsCommutativeMinToMin.{u2} β _inst_1) (instIsAssociativeMinToMin.{u2} β _inst_1) b f s)) (And (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c b) (forall (x : α), (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) -> (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (f x))))
Case conversion may be inaccurate. Consider using '#align finset.lt_fold_min Finset.lt_fold_minₓ'. -/
theorem lt_fold_min : c < s.fold min b f ↔ c < b ∧ ∀ x ∈ s, c < f x :=
  fold_op_rel_iff_and fun x y z => lt_min_iff
#align finset.lt_fold_min Finset.lt_fold_min

/- warning: finset.fold_min_lt -> Finset.fold_min_lt is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (Finset.fold.{u1, u2} α β (LinearOrder.min.{u2} β _inst_1) (inf_isCommutative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (inf_isAssociative.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s) c) (Or (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) b c) (Exists.{succ u1} α (fun (x : α) => Exists.{0} (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) (fun (H : Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) => LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (f x) c))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (Finset.fold.{u1, u2} α β (Min.min.{u2} β (LinearOrder.toMin.{u2} β _inst_1)) (instIsCommutativeMinToMin.{u2} β _inst_1) (instIsAssociativeMinToMin.{u2} β _inst_1) b f s) c) (Or (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) b c) (Exists.{succ u1} α (fun (x : α) => And (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (f x) c))))
Case conversion may be inaccurate. Consider using '#align finset.fold_min_lt Finset.fold_min_ltₓ'. -/
theorem fold_min_lt : s.fold min b f < c ↔ b < c ∨ ∃ x ∈ s, f x < c :=
  by
  show _ > _ ↔ _
  apply fold_op_rel_iff_or
  intro x y z
  show _ < _ ↔ _
  exact min_lt_iff
#align finset.fold_min_lt Finset.fold_min_lt

/- warning: finset.fold_max_le -> Finset.fold_max_le is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (Finset.fold.{u1, u2} α β (LinearOrder.max.{u2} β _inst_1) (sup_isCommutative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (sup_isAssociative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s) c) (And (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) b c) (forall (x : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) -> (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (f x) c)))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (Finset.fold.{u1, u2} α β (Max.max.{u2} β (LinearOrder.toMax.{u2} β _inst_1)) (instIsCommutativeMaxToMax.{u2} β _inst_1) (instIsAssociativeMaxToMax.{u2} β _inst_1) b f s) c) (And (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) b c) (forall (x : α), (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) -> (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (f x) c)))
Case conversion may be inaccurate. Consider using '#align finset.fold_max_le Finset.fold_max_leₓ'. -/
theorem fold_max_le : s.fold max b f ≤ c ↔ b ≤ c ∧ ∀ x ∈ s, f x ≤ c :=
  by
  show _ ≥ _ ↔ _
  apply fold_op_rel_iff_and
  intro x y z
  show _ ≤ _ ↔ _
  exact max_le_iff
#align finset.fold_max_le Finset.fold_max_le

/- warning: finset.le_fold_max -> Finset.le_fold_max is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (Finset.fold.{u1, u2} α β (LinearOrder.max.{u2} β _inst_1) (sup_isCommutative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (sup_isAssociative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s)) (Or (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c b) (Exists.{succ u1} α (fun (x : α) => Exists.{0} (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) (fun (H : Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) => LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (f x)))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (Finset.fold.{u1, u2} α β (Max.max.{u2} β (LinearOrder.toMax.{u2} β _inst_1)) (instIsCommutativeMaxToMax.{u2} β _inst_1) (instIsAssociativeMaxToMax.{u2} β _inst_1) b f s)) (Or (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c b) (Exists.{succ u1} α (fun (x : α) => And (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (f x)))))
Case conversion may be inaccurate. Consider using '#align finset.le_fold_max Finset.le_fold_maxₓ'. -/
theorem le_fold_max : c ≤ s.fold max b f ↔ c ≤ b ∨ ∃ x ∈ s, c ≤ f x :=
  fold_op_rel_iff_or fun x y z => le_max_iff
#align finset.le_fold_max Finset.le_fold_max

/- warning: finset.fold_max_lt -> Finset.fold_max_lt is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (Finset.fold.{u1, u2} α β (LinearOrder.max.{u2} β _inst_1) (sup_isCommutative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (sup_isAssociative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s) c) (And (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) b c) (forall (x : α), (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) -> (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) (f x) c)))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (Finset.fold.{u1, u2} α β (Max.max.{u2} β (LinearOrder.toMax.{u2} β _inst_1)) (instIsCommutativeMaxToMax.{u2} β _inst_1) (instIsAssociativeMaxToMax.{u2} β _inst_1) b f s) c) (And (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) b c) (forall (x : α), (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) -> (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) (f x) c)))
Case conversion may be inaccurate. Consider using '#align finset.fold_max_lt Finset.fold_max_ltₓ'. -/
theorem fold_max_lt : s.fold max b f < c ↔ b < c ∧ ∀ x ∈ s, f x < c :=
  by
  show _ > _ ↔ _
  apply fold_op_rel_iff_and
  intro x y z
  show _ < _ ↔ _
  exact max_lt_iff
#align finset.fold_max_lt Finset.fold_max_lt

/- warning: finset.lt_fold_max -> Finset.lt_fold_max is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (Finset.fold.{u1, u2} α β (LinearOrder.max.{u2} β _inst_1) (sup_isCommutative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) (sup_isAssociative.{u2} β (Lattice.toSemilatticeSup.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))) b f s)) (Or (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c b) (Exists.{succ u1} α (fun (x : α) => Exists.{0} (Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) (fun (H : Membership.Mem.{u1, u1} α (Finset.{u1} α) (Finset.hasMem.{u1} α) x s) => LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))) c (f x)))))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} {b : β} {s : Finset.{u1} α} [_inst_1 : LinearOrder.{u2} β] (c : β), Iff (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (Finset.fold.{u1, u2} α β (Max.max.{u2} β (LinearOrder.toMax.{u2} β _inst_1)) (instIsCommutativeMaxToMax.{u2} β _inst_1) (instIsAssociativeMaxToMax.{u2} β _inst_1) b f s)) (Or (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c b) (Exists.{succ u1} α (fun (x : α) => And (Membership.mem.{u1, u1} α (Finset.{u1} α) (Finset.instMembershipFinset.{u1} α) x s) (LT.lt.{u2} β (Preorder.toLT.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) c (f x)))))
Case conversion may be inaccurate. Consider using '#align finset.lt_fold_max Finset.lt_fold_maxₓ'. -/
theorem lt_fold_max : c < s.fold max b f ↔ c < b ∨ ∃ x ∈ s, c < f x :=
  fold_op_rel_iff_or fun x y z => lt_max_iff
#align finset.lt_fold_max Finset.lt_fold_max

/- warning: finset.fold_max_add -> Finset.fold_max_add is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} [_inst_1 : LinearOrder.{u2} β] [_inst_2 : Add.{u2} β] [_inst_3 : CovariantClass.{u2, u2} β β (Function.swap.{succ u2, succ u2, succ u2} β β (fun (ᾰ : β) (ᾰ : β) => β) (HAdd.hAdd.{u2, u2, u2} β β β (instHAdd.{u2} β _inst_2))) (LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (LinearOrder.toLattice.{u2} β _inst_1))))))] (n : WithBot.{u2} β) (s : Finset.{u1} α), Eq.{succ u2} (WithBot.{u2} β) (Finset.fold.{u1, u2} α (WithBot.{u2} β) (LinearOrder.max.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1)) (sup_isCommutative.{u2} (WithBot.{u2} β) (Lattice.toSemilatticeSup.{u2} (WithBot.{u2} β) (WithBot.lattice.{u2} β (LinearOrder.toLattice.{u2} β _inst_1)))) (sup_isAssociative.{u2} (WithBot.{u2} β) (Lattice.toSemilatticeSup.{u2} (WithBot.{u2} β) (WithBot.lattice.{u2} β (LinearOrder.toLattice.{u2} β _inst_1)))) (Bot.bot.{u2} (WithBot.{u2} β) (WithBot.hasBot.{u2} β)) (fun (x : α) => HAdd.hAdd.{u2, u2, u2} (WithBot.{u2} β) (WithBot.{u2} β) (WithBot.{u2} β) (instHAdd.{u2} (WithBot.{u2} β) (WithBot.hasAdd.{u2} β _inst_2)) ((fun (a : Type.{u2}) (b : Type.{u2}) [self : HasLiftT.{succ u2, succ u2} a b] => self.0) β (WithBot.{u2} β) (HasLiftT.mk.{succ u2, succ u2} β (WithBot.{u2} β) (CoeTCₓ.coe.{succ u2, succ u2} β (WithBot.{u2} β) (WithBot.hasCoeT.{u2} β))) (f x)) n) s) (HAdd.hAdd.{u2, u2, u2} (WithBot.{u2} β) (WithBot.{u2} β) (WithBot.{u2} β) (instHAdd.{u2} (WithBot.{u2} β) (WithBot.hasAdd.{u2} β _inst_2)) (Finset.fold.{u1, u2} α (WithBot.{u2} β) (LinearOrder.max.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1)) (sup_isCommutative.{u2} (WithBot.{u2} β) (Lattice.toSemilatticeSup.{u2} (WithBot.{u2} β) (WithBot.lattice.{u2} β (LinearOrder.toLattice.{u2} β _inst_1)))) (sup_isAssociative.{u2} (WithBot.{u2} β) (Lattice.toSemilatticeSup.{u2} (WithBot.{u2} β) (WithBot.lattice.{u2} β (LinearOrder.toLattice.{u2} β _inst_1)))) (Bot.bot.{u2} (WithBot.{u2} β) (WithBot.hasBot.{u2} β)) (Function.comp.{succ u1, succ u2, succ u2} α β (WithBot.{u2} β) ((fun (a : Type.{u2}) (b : Type.{u2}) [self : HasLiftT.{succ u2, succ u2} a b] => self.0) β (WithBot.{u2} β) (HasLiftT.mk.{succ u2, succ u2} β (WithBot.{u2} β) (CoeTCₓ.coe.{succ u2, succ u2} β (WithBot.{u2} β) (WithBot.hasCoeT.{u2} β)))) f) s) n)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> β} [_inst_1 : LinearOrder.{u2} β] [_inst_2 : Add.{u2} β] [_inst_3 : CovariantClass.{u2, u2} β β (Function.swap.{succ u2, succ u2, succ u2} β β (fun (ᾰ : β) (ᾰ : β) => β) (fun (x._@.Mathlib.Data.Finset.Fold._hyg.3739 : β) (x._@.Mathlib.Data.Finset.Fold._hyg.3741 : β) => HAdd.hAdd.{u2, u2, u2} β β β (instHAdd.{u2} β _inst_2) x._@.Mathlib.Data.Finset.Fold._hyg.3739 x._@.Mathlib.Data.Finset.Fold._hyg.3741)) (fun (x._@.Mathlib.Data.Finset.Fold._hyg.3754 : β) (x._@.Mathlib.Data.Finset.Fold._hyg.3756 : β) => LE.le.{u2} β (Preorder.toLE.{u2} β (PartialOrder.toPreorder.{u2} β (SemilatticeInf.toPartialOrder.{u2} β (Lattice.toSemilatticeInf.{u2} β (DistribLattice.toLattice.{u2} β (instDistribLattice.{u2} β _inst_1)))))) x._@.Mathlib.Data.Finset.Fold._hyg.3754 x._@.Mathlib.Data.Finset.Fold._hyg.3756)] (n : WithBot.{u2} β) (s : Finset.{u1} α), Eq.{succ u2} (WithBot.{u2} β) (Finset.fold.{u1, u2} α (WithBot.{u2} β) (Max.max.{u2} (WithBot.{u2} β) (LinearOrder.toMax.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1))) (instIsCommutativeMaxToMax.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1)) (instIsAssociativeMaxToMax.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1)) (Bot.bot.{u2} (WithBot.{u2} β) (WithBot.bot.{u2} β)) (fun (x : α) => HAdd.hAdd.{u2, u2, u2} (WithBot.{u2} β) (WithBot.{u2} β) (WithBot.{u2} β) (instHAdd.{u2} (WithBot.{u2} β) (WithBot.add.{u2} β _inst_2)) (WithBot.some.{u2} β (f x)) n) s) (HAdd.hAdd.{u2, u2, u2} (WithBot.{u2} β) (WithBot.{u2} β) (WithBot.{u2} β) (instHAdd.{u2} (WithBot.{u2} β) (WithBot.add.{u2} β _inst_2)) (Finset.fold.{u1, u2} α (WithBot.{u2} β) (Max.max.{u2} (WithBot.{u2} β) (LinearOrder.toMax.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1))) (instIsCommutativeMaxToMax.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1)) (instIsAssociativeMaxToMax.{u2} (WithBot.{u2} β) (WithBot.linearOrder.{u2} β _inst_1)) (Bot.bot.{u2} (WithBot.{u2} β) (WithBot.bot.{u2} β)) (Function.comp.{succ u1, succ u2, succ u2} α β (WithBot.{u2} β) (WithBot.some.{u2} β) f) s) n)
Case conversion may be inaccurate. Consider using '#align finset.fold_max_add Finset.fold_max_addₓ'. -/
theorem fold_max_add [Add β] [CovariantClass β β (Function.swap (· + ·)) (· ≤ ·)] (n : WithBot β)
    (s : Finset α) : (s.fold max ⊥ fun x : α => ↑(f x) + n) = s.fold max ⊥ (coe ∘ f) + n := by
  classical apply s.induction_on <;> simp (config := { contextual := true }) [max_add_add_right]
#align finset.fold_max_add Finset.fold_max_add

end Order

end Fold

end Finset


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

! This file was ported from Lean 3 source module category_theory.monad.equiv_mon
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Monad.Basic
import Mathbin.CategoryTheory.Monoidal.End
import Mathbin.CategoryTheory.Monoidal.Mon_

/-!

# The equivalence between `Monad C` and `Mon_ (C ⥤ C)`.

A monad "is just" a monoid in the category of endofunctors.

# Definitions/Theorems

1. `to_Mon` associates a monoid object in `C ⥤ C` to any monad on `C`.
2. `Monad_to_Mon` is the functorial version of `to_Mon`.
3. `of_Mon` associates a monad on `C` to any monoid object in `C ⥤ C`.
4. `Monad_Mon_equiv` is the equivalence between `Monad C` and `Mon_ (C ⥤ C)`.

-/

namespace CategoryTheory

open Category

universe v u

-- morphism levels before object levels. See note [category_theory universes].
variable {C : Type u} [Category.{v} C]

namespace Monad

attribute [local instance, local reducible] endofunctor_monoidal_category

/-- To every `Monad C` we associated a monoid object in `C ⥤ C`.-/
@[simps]
def toMon : Monad C → Mon_ (C ⥤ C) := fun M =>
  { pt := (M : C ⥤ C)
    one := M.η
    mul := M.μ
    one_mul' := by
      ext
      simp
    -- `obviously` provides this, but slowly
    mul_one' := by
      ext
      simp
    -- `obviously` provides this, but slowly
    mul_assoc' := by
      ext
      dsimp
      simp [M.assoc] }
#align category_theory.Monad.to_Mon CategoryTheory.Monad.toMon

variable (C)

/-- Passing from `Monad C` to `Mon_ (C ⥤ C)` is functorial. -/
@[simps]
def monadToMon : Monad C ⥤ Mon_ (C ⥤ C) where
  obj := toMon
  map _ _ f := { Hom := f.toNatTrans }
  map_id' := by
    intro X
    rfl
  -- `obviously` provides this, but slowly
  map_comp' := by
    intro X Y Z f g
    rfl
#align category_theory.Monad.Monad_to_Mon CategoryTheory.Monad.monadToMon

variable {C}

/-- To every monoid object in `C ⥤ C` we associate a `Monad C`. -/
@[simps]
def ofMon : Mon_ (C ⥤ C) → Monad C := fun M =>
  { toFunctor := M.pt
    η' := M.one
    μ' := M.mul
    left_unit' := fun X =>
      by
      rw [← M.one.id_hcomp_app, ← nat_trans.comp_app, M.mul_one]
      rfl
    right_unit' := fun X =>
      by
      rw [← M.one.hcomp_id_app, ← nat_trans.comp_app, M.one_mul]
      rfl
    assoc' := fun X => by
      rw [← nat_trans.hcomp_id_app, ← nat_trans.comp_app]
      simp }
#align category_theory.Monad.of_Mon CategoryTheory.Monad.ofMon

variable (C)

/-- Passing from `Mon_ (C ⥤ C)` to `Monad C` is functorial. -/
@[simps]
def monToMonad : Mon_ (C ⥤ C) ⥤ Monad C where
  obj := ofMon
  map _ _ f :=
    {-- `finish` closes this goal
        f.Hom with
      app_η' := by
        intro X
        erw [← nat_trans.comp_app, f.one_hom]
        rfl
      app_μ' := by
        intro X
        erw [← nat_trans.comp_app, f.mul_hom]
        simpa only [nat_trans.naturality, nat_trans.hcomp_app, assoc, nat_trans.comp_app,
          of_Mon_μ] }
#align category_theory.Monad.Mon_to_Monad CategoryTheory.Monad.monToMonad

namespace MonadMonEquiv

variable {C}

/-- Isomorphism of functors used in `Monad_Mon_equiv` -/
@[simps (config := { rhsMd := semireducible })]
def counitIso : monToMonad C ⋙ monadToMon C ≅ 𝟭 _
    where
  Hom := { app := fun _ => { Hom := 𝟙 _ } }
  inv := { app := fun _ => { Hom := 𝟙 _ } }
  hom_inv_id' := by
    ext
    simp
  -- `obviously` provides these, but slowly
  inv_hom_id' := by
    ext
    simp
#align category_theory.Monad.Monad_Mon_equiv.counit_iso CategoryTheory.Monad.MonadMonEquiv.counitIso

/-- Auxiliary definition for `Monad_Mon_equiv` -/
@[simps]
def unitIsoHom : 𝟭 _ ⟶ monadToMon C ⋙ monToMonad C where app _ := { app := fun _ => 𝟙 _ }
#align category_theory.Monad.Monad_Mon_equiv.unit_iso_hom CategoryTheory.Monad.MonadMonEquiv.unitIsoHom

/-- Auxiliary definition for `Monad_Mon_equiv` -/
@[simps]
def unitIsoInv : monadToMon C ⋙ monToMonad C ⟶ 𝟭 _ where app _ := { app := fun _ => 𝟙 _ }
#align category_theory.Monad.Monad_Mon_equiv.unit_iso_inv CategoryTheory.Monad.MonadMonEquiv.unitIsoInv

/-- Isomorphism of functors used in `Monad_Mon_equiv` -/
@[simps]
def unitIso : 𝟭 _ ≅ monadToMon C ⋙ monToMonad C
    where
  Hom := unitIsoHom
  inv := unitIsoInv
  hom_inv_id' := by
    ext
    simp
  -- `obviously` provides these, but slowly
  inv_hom_id' := by
    ext
    simp
#align category_theory.Monad.Monad_Mon_equiv.unit_iso CategoryTheory.Monad.MonadMonEquiv.unitIso

end MonadMonEquiv

open MonadMonEquiv

/-- Oh, monads are just monoids in the category of endofunctors (equivalence of categories). -/
@[simps]
def monadMonEquiv : Monad C ≌ Mon_ (C ⥤ C)
    where
  Functor := monadToMon _
  inverse := monToMonad _
  unitIso := unitIso
  counitIso := counitIso
  functor_unitIso_comp' := by
    intro X
    ext
    dsimp
    simp
#align category_theory.Monad.Monad_Mon_equiv CategoryTheory.Monad.monadMonEquiv

-- `obviously`, slowly
-- Sanity check
example (A : Monad C) {X : C} : ((monadMonEquiv C).unitIso.app A).Hom.app X = 𝟙 _ :=
  rfl

end Monad

end CategoryTheory


import tactic

import group_theory.index
import group_theory.p_group
import group_theory.quotient_group
import group_theory.coset
import group_theory.specific_groups.cyclic
import group_theory.abelianization
import data.finite.basic
import data.finite.card
import data.real.basic
import group_theory.subgroup.basic

open fintype
open is_p_group
open group

/-- A group that is commutative can be turned into a comm_group
(See https://github.com/leanprover-community/mathlib/pull/14960#issuecomment-1178880188)
-/
instance {G} : can_lift (group G) (comm_group G) :=
{ coe := @comm_group.to_group G,
  cond := λ g, by exactI (∀ x y : G, commute x y),
  prf := λ g hg, ⟨{mul_comm := hg, ..g}, by cases g; refl⟩ }

/-- My lemma -/
lemma group.center_eq_top_of_comm {H : Type*} [group H] (h : ∀ a b : H, a * b = b * a) : subgroup.center H = ⊤ :=
begin
  rw subgroup.eq_top_iff',
  intros x y,
  exact (h y x),
end

lemma group.of_comm_center_eq_top {H : Type*} [group H] (h : (subgroup.center H = ⊤)) : (∀ a b : H, a * b = b * a) :=
begin
  rw subgroup.eq_top_iff' at h,
  intros x y,
  rw (h x),
end

variables (p : ℕ) [fact (nat.prime p)]
variables (G : Type*) [fintype G] [group G]

def order_psq : Prop := card G = p^2

namespace order_psq

lemma has_ord_psq : order_psq p G → ∃ q : ℕ , card G = q^2 :=
begin
  intro h1,
  use p,
  apply h1,
end

section G_has_order_psq

variables (hG : order_psq p G)
include hG

lemma p_group : is_p_group p G :=
begin
  exact of_card hG,
end

lemma psq_center_nontrivial [nontrivial G] : nontrivial (subgroup.center G) :=
center_nontrivial (p_group p G hG)

open_locale classical

@[instance] def quotient_with_center_is_fintype : finite (G ⧸ subgroup.center G) :=
infer_instance

lemma center_index_not_prime : ¬ nat.prime (fintype.card (G ⧸ subgroup.center G)) :=
begin
  intro h1,
  haveI : fact(nat.prime (card (G ⧸ subgroup.center G))) := ⟨h1⟩,
  haveI := is_cyclic_of_prime_card (rfl : card (G ⧸ subgroup.center G) = _),
  --letI := comm_group_of_cycle_center_quotient (quotient_group.mk' (subgroup.center G)) (by simp),
  have h2 := commutative_of_cyclic_center_quotient (quotient_group.mk' (subgroup.center G)) (by simp),
  unfreezingI { lift ‹group G› to comm_group G using h2 },
  have h3 : subgroup.center G = ⊤,
  {
    exact comm_group.center_eq_top,  -- working
  },
  have h4 : card (G ⧸ subgroup.center G) = 1,
  {
    simp_rw [h3, ← nat.card_eq_fintype_card],
    exact subgroup.index_top,
  },
  rw h4 at h1,
  exact nat.not_prime_one h1,
end

open_locale cardinal

#check subgroup.card_mul_index (subgroup.center G)

theorem order_psq_are_abelian [nontrivial G] : (∀ x1 x2 : G, x1 * x2 = x2 * x1) :=
begin
  apply group.of_comm_center_eq_top,
  have h1 : (card (subgroup.center G) ∣ card (G)),
  {
    exact (subgroup.center G).card_subgroup_dvd_card,
  },
  have h2 : (card G = p^2),
  {
    exact hG,
  },
  rw h2 at h1,
  rw nat.dvd_prime_pow at h1,
  rcases h1 with ⟨k, hk1, hk2⟩,
  swap,
  apply fact.out,
  interval_cases k,
  -- # |Z(G)| = 1
  {
    simp at hk2,
    exfalso,
    have h31 : nontrivial (subgroup.center G),
    {
      exact center_nontrivial (p_group p G hG),
    },
    rw subgroup.nontrivial_iff_exists_ne_one at h31,
    rcases h31 with ⟨x, hx, hxx⟩,
    have h32 : subgroup.center G = ⊥,
    {
      --have hcalc : card (subgroup.center G) ≤ 1, linarith,
      --rw ← subgroup.card_le_one_iff_eq_bot,
      --exact hcalc,
      --simp_rw hk2,
      rw [← subgroup.card_le_one_iff_eq_bot, hk2],
    },
    have hxx' : x = 1,
    {
      --finish,
      --rw h32 at hx,
      rw subgroup.eq_bot_iff_forall at h32,
      exact h32 x hx,
    },
    contradiction,
  },
  -- # |Z(G)| = p
  {
    exfalso,
    have h41 : p * (subgroup.center G).index = p^2,
    {
      simp at hk2,
      simp_rw ← nat.card_eq_fintype_card at hk2,
      simp_rw ← nat.card_eq_fintype_card at h2,
      rw [← h2, ← hk2],
      exact subgroup.card_mul_index (subgroup.center G),
    },
    have h42 : (subgroup.center G).index = p,
    {
      have hcalc : p * (subgroup.center G).index = p * p,
      {
        simp_rw h41,
        exact sq p,
      },
      simp at hcalc,
      have hcalc2 : ¬ (p = 0),
      {
        intro hcp,
        have hcp2 := _inst_1.out,
        rw hcp at hcp2,
        have hcp3 := nat.not_prime_zero,
        contradiction,
      },
      cases hcalc with hcl hcr,
      {
        exact hcl,
      },
      {
        exfalso,
        contradiction,
      },
      --finish,  -- works
      --sorry,  -- Compiles much faster than it would with `finish`
    },
    have h43 : nat.prime (card (G ⧸ subgroup.center G)),
    {
      have hcalc : nat.card (G ⧸ subgroup.center G) = p, exact h42,
      rw nat.card_eq_fintype_card at hcalc,
      rw hcalc,
      exact _inst_1.out,
    },
    have h44 : ¬ nat.prime (fintype.card (G ⧸ subgroup.center G)),
    {
      apply center_index_not_prime p G,
      exact hG,
    },
    contradiction,
  },
  -- # |Z(G)| = p^2
  {
    apply subgroup.eq_top_of_card_eq,
    rw h2,
    exact hk2,
  },
end

end G_has_order_psq

end order_psq


import Lean4Axiomatic.Integer.Impl.Generic.Sign
import Lean4Axiomatic.Integer.Impl.Difference.Multiplication
import Lean4Axiomatic.Integer.Impl.Difference.Negation

namespace Lean4Axiomatic.Integer.Impl.Difference

variable {ℕ : Type} [Natural ℕ]

open Coe (coe)
open Signed (Negative Positive)

/--
A `Difference` of natural numbers is zero exactly when the numbers are
equivalent.

**Property intuition**: Subtracting a value from itself always gives zero.

**Proof intuition**: Expand the definition of `Difference` equivalence and add
or remove zeros.
-/
theorem zero_diff_eqv {n m : ℕ} : n——m ≃ 0 ↔ n ≃ m := by
  apply Iff.intro
  case mp =>
    intro (_ : n——m ≃ 0)
    show n ≃ m
    have : n——m ≃ 0——0 := ‹n——m ≃ 0›
    have : n + 0 ≃ 0 + m := ‹n——m ≃ 0——0›
    exact Natural.add_swapped_zeros_eqv.mp ‹n + 0 ≃ 0 + m›
  case mpr =>
    intro (_ : n ≃ m)
    show n——m ≃ 0
    show n——m ≃ 0——0
    show n + 0 ≃ 0 + m
    exact Natural.add_swapped_zeros_eqv.mpr ‹n ≃ m›

/--
A `Difference` of natural numbers is negative exactly when the first component
is less than the second.

**Property intuition**: Subtracting a larger value from a smaller will give a
negative result.

**Proof intuition**: There's no simple trick for this proof. Just expand the
definitions of `Negative` and `(· < ·)` and show that the equivalence for one
implies the other.
-/
theorem neg_diff_lt {n m : ℕ} : Negative (n——m) ↔ n < m := by
  have neg_diff {k : ℕ} : 0——k ≃ -1 * coe k := by
    apply Rel.symm
    calc
      (-1) * coe k ≃ _ := Rel.refl
      (-1) * k——0  ≃ _ := mul_neg_one
      (-(k——0))    ≃ _ := Rel.refl
      0——k         ≃ _ := Rel.refl
  apply Iff.intro
  case mp =>
    intro (_ : Negative (n——m))
    show n < m
    apply Natural.lt_defn_add.mpr
    show ∃ (k : ℕ), Positive k ∧ m ≃ n + k
    have
      (NonzeroWithSign.intro (k : ℕ) (_ : Positive k) (_ : n——m ≃ -1 * coe k))
        := Generic.negative_iff_sign_neg1.mp ‹Negative (n——m)›
    have : n——m ≃ 0——k := Rel.trans ‹n——m ≃ -1 * coe k› (Rel.symm neg_diff)
    have : n + k ≃ 0 + m := ‹n——m ≃ 0——k›
    have : m ≃ n + k := calc
      m     ≃ _ := Rel.symm AA.identL
      0 + m ≃ _ := Rel.symm ‹n + k ≃ 0 + m›
      n + k ≃ _ := Rel.refl
    exact Exists.intro k (And.intro ‹Positive k› ‹m ≃ n + k›)
  case mpr =>
    intro (_ : n < m)
    show Negative (n——m)
    apply Generic.negative_iff_sign_neg1.mpr
    show NonzeroWithSign (n——m) (-1)
    have (Exists.intro k (And.intro (_ : Positive k) (_ : m ≃ n + k))) :=
      Natural.lt_defn_add.mp ‹n < m›
    apply NonzeroWithSign.intro k ‹Positive k›
    show n——m ≃ -1 * coe k
    have : 0——k ≃ -1 * coe k := neg_diff
    apply (Rel.trans · ‹0——k ≃ -1 * coe k›)
    show n——m ≃ 0——k
    show n + k ≃ 0 + m
    calc
      n + k ≃ _ := Rel.symm ‹m ≃ n + k›
      m     ≃ _ := Rel.symm AA.identL
      0 + m ≃ _ := Rel.refl

/--
A `Difference` of natural numbers is positive exactly when the first component
is greater than the second.

**Property intuition**: Subtracting a smaller value from a larger will give a
positive result.

**Proof intuition**: By definition, `n > m` is the same as `m < n`. And we
already know (from `neg_diff_lt`) that `m < n` is equivalent to
`Negative (m——n)`. So if we can show that `Positive (n——m)` iff
`Negative (m——n)`, then that will prove the result.
-/
theorem pos_diff_gt {n m : ℕ} : Positive (n——m) ↔ n > m := by
  apply Iff.intro
  case mp =>
    intro (_ : Positive (n——m))
    have
      (NonzeroWithSign.intro (k : ℕ) (_ : Positive k) (_ : n——m ≃ 1 * coe k))
        := Generic.positive_iff_sign_pos1.mp ‹Positive (n——m)›
    show m < n
    apply neg_diff_lt.mp
    show Negative (m——n)
    apply Generic.negative_iff_sign_neg1.mpr
    show NonzeroWithSign (m——n) (-1)
    apply NonzeroWithSign.intro k ‹Positive k›
    show m——n ≃ -1 * coe k
    calc
      m——n           ≃ _ := Rel.symm neg_involutive
      (-(-(m——n)))   ≃ _ := Rel.refl
      (-(n——m))      ≃ _ := AA.subst₁ ‹n——m ≃ 1 * coe k›
      (-(1 * coe k)) ≃ _ := AA.scompatL
      (-1) * coe k   ≃ _ := Rel.refl
  case mpr =>
    intro (_ : m < n)
    show Positive (n——m)
    apply Generic.positive_iff_sign_pos1.mpr
    show NonzeroWithSign (n——m) 1
    have : Negative (m——n) := neg_diff_lt.mpr ‹m < n›
    have
      (NonzeroWithSign.intro (k : ℕ) (_ : Positive k) (_ : m——n ≃ -1 * coe k))
        := Generic.negative_iff_sign_neg1.mp ‹Negative (m——n)›
    apply NonzeroWithSign.intro k ‹Positive k›
    show n——m ≃ 1 * coe k
    calc
      n——m              ≃ _ := Rel.symm neg_involutive
      (-(-(n——m)))      ≃ _ := Rel.refl
      (-(m——n))         ≃ _ := AA.subst₁ ‹m——n ≃ -1 * coe k›
      (-(-1 * coe k))   ≃ _ := AA.subst₁ (Rel.symm AA.scompatL)
      (-(-(1 * coe k))) ≃ _ := neg_involutive
      1 * coe k         ≃ _ := Rel.refl

/--
Every natural number difference is equivalent to exactly one of the following:
* zero;
* a positive natural number;
* the negation of a positive natural number.

**Proof intuition**: This property is equivalent to the trichotomy of order on
the natural number components of differences. Given a difference `n——m`, it is
equal to
* zero when `n ≃ m`;
* a positive natural number when `n > m`;
* the negation of a positive natural number when `n < m`.

The whole proof is just translating from one form of trichotomy into the other.
-/
theorem sign_trichotomy
    (a : Difference ℕ) : AA.ExactlyOneOfThree (a ≃ 0) (Positive a) (Negative a)
    := by
  revert a; intro (n——m)
  show AA.ExactlyOneOfThree (n——m ≃ 0) (Positive (n——m)) (Negative (n——m))
  have natOrderTri : AA.ExactlyOneOfThree (n < m) (n ≃ m) (n > m) :=
    Natural.trichotomy n m
  apply AA.ExactlyOneOfThree.mk
  case atLeastOne =>
    show AA.OneOfThree (n——m ≃ 0) (Positive (n——m)) (Negative (n——m))
    match natOrderTri.atLeastOne with
    | AA.OneOfThree.first (_ : n < m) =>
      have : Negative (n——m) := neg_diff_lt.mpr ‹n < m›
      exact AA.OneOfThree.third ‹Negative (n——m)›
    | AA.OneOfThree.second (_ : n ≃ m) =>
      have : n——m ≃ 0 := zero_diff_eqv.mpr ‹n ≃ m›
      exact AA.OneOfThree.first ‹n——m ≃ 0›
    | AA.OneOfThree.third (_ : n > m) =>
      have : Positive (n——m) := pos_diff_gt.mpr ‹n > m›
      exact AA.OneOfThree.second ‹Positive (n——m)›
  case atMostOne =>
    intro (h : AA.TwoOfThree (n——m ≃ 0) (Positive (n——m)) (Negative (n——m)))
    have twoOfThree : AA.TwoOfThree (n < m) (n ≃ m) (n > m) := match h with
    | AA.TwoOfThree.oneAndTwo (_ : n——m ≃ 0) (_ : Positive (n——m)) =>
      have : n ≃ m := zero_diff_eqv.mp ‹n——m ≃ 0›
      have : n > m := pos_diff_gt.mp ‹Positive (n——m)›
      AA.TwoOfThree.twoAndThree ‹n ≃ m› ‹n > m›
    | AA.TwoOfThree.oneAndThree (_ : n——m ≃ 0) (_ : Negative (n——m)) =>
      have : n < m := neg_diff_lt.mp ‹Negative (n——m)›
      have : n ≃ m := zero_diff_eqv.mp ‹n——m ≃ 0›
      AA.TwoOfThree.oneAndTwo ‹n < m› ‹n ≃ m›
    | AA.TwoOfThree.twoAndThree (_ : Positive (n——m)) (_ : Negative (n——m)) =>
      have : n < m := neg_diff_lt.mp ‹Negative (n——m)›
      have : n > m := pos_diff_gt.mp ‹Positive (n——m)›
      AA.TwoOfThree.oneAndThree ‹n < m› ‹n > m›
    show False
    have notTwoOfThree : ¬ AA.TwoOfThree (n < m) (n ≃ m) (n > m) :=
      natOrderTri.atMostOne
    exact absurd twoOfThree notTwoOfThree

/--
Implementation of the
[signum function](https://en.wikipedia.org/wiki/Sign_function) for differences.

**Definition intuition**: For a difference `n——m`, gives the correct sign value
according to the ordering of `n` and `m`.
-/
def sgn : Difference ℕ → Difference ℕ
| n——m => ord_sgn (compare n m)

/--
Zero is the only difference with sign value zero.

**Property intuition**: Zero is neither positive nor negative, so it gets its
own sign value.

**Proof intuition**: All differences with value zero have components that are
equivalent. The `sgn` function evaluates to zero in that case.
-/
theorem sgn_zero {a : Difference ℕ} : a ≃ 0 ↔ sgn a ≃ 0 := by
  revert a; intro (n——m)
  apply Iff.intro
  case mp =>
    intro (_ : n——m ≃ 0)
    show sgn (n——m) ≃ 0
    have : n ≃ m := zero_diff_eqv.mp ‹n——m ≃ 0›
    have : compare n m = Ordering.eq := Natural.compare_eq.mpr this
    have : ord_sgn (compare n m) ≃ ord_sgn Ordering.eq :=
      ord_sgn_subst (ℤ := Difference ℕ) this
    have : ord_sgn (compare n m) ≃ 0 := this
    have : sgn (n——m) ≃ 0 := this
    exact this
  case mpr =>
    intro (_ : sgn (n——m) ≃ 0)
    show n——m ≃ 0
    have : ord_sgn (compare n m) ≃ 0 := ‹sgn (n——m) ≃ 0›
    have : ord_sgn (compare n m) ≃ ord_sgn Ordering.eq := this
    have : compare n m = Ordering.eq := ord_sgn_inject this
    have : n ≃ m := Natural.compare_eq.mp this
    have : n——m ≃ 0 := zero_diff_eqv.mpr this
    exact this

/--
Only positive differences have sign value one.

**Property intuition**: The definition of the `sgn` function is for all, and
only, positive integers to have sign value one.

**Proof intuition**: All positive differences have a first component that's
greater than their second component. The `sgn` function evaluates to one in
that case.
-/
theorem sgn_positive {a : Difference ℕ} : Positive a ↔ sgn a ≃ 1 := by
  revert a; intro (n——m)
  apply Iff.intro
  case mp =>
    intro (_ : Positive (n——m))
    show sgn (n——m) ≃ 1
    have : n > m := pos_diff_gt.mp ‹Positive (n——m)›
    have : compare n m = Ordering.gt := Natural.compare_gt.mpr this
    have : ord_sgn (compare n m) ≃ ord_sgn Ordering.gt :=
      ord_sgn_subst (ℤ := Difference ℕ) this
    have : ord_sgn (compare n m) ≃ 1 := this
    have : sgn (n——m) ≃ 1 := this
    exact this
  case mpr =>
    intro (_ : sgn (n——m) ≃ 1)
    show Positive (n——m)
    have : ord_sgn (compare n m) ≃ 1 := ‹sgn (n——m) ≃ 1›
    have : ord_sgn (compare n m) ≃ ord_sgn Ordering.gt := this
    have : compare n m = Ordering.gt := ord_sgn_inject this
    have : n > m := Natural.compare_gt.mp this
    have : Positive (n——m) := pos_diff_gt.mpr this
    exact this

/--
Only negative differences have sign value negative one.

**Property intuition**: The definition of the `sgn` function is for all, and
only, negative integers to have sign value negative one.

**Proof intuition**: All negative differences have a first component that's
less than their second component. The `sgn` function evaluates to negative one
in that case.
-/
theorem sgn_negative {a : Difference ℕ} : Negative a ↔ sgn a ≃ -1 := by
  revert a; intro (n——m)
  apply Iff.intro
  case mp =>
    intro (_ : Negative (n——m))
    show sgn (n——m) ≃ -1
    have : n < m := neg_diff_lt.mp ‹Negative (n——m)›
    have : compare n m = Ordering.lt := Natural.compare_lt.mpr this
    have : ord_sgn (compare n m) ≃ ord_sgn Ordering.lt :=
      ord_sgn_subst (ℤ := Difference ℕ) this
    have : ord_sgn (compare n m) ≃ -1 := this
    have : sgn (n——m) ≃ -1 := this
    exact this
  case mpr =>
    intro (_ : sgn (n——m) ≃ -1)
    show Negative (n——m)
    have : ord_sgn (compare n m) ≃ -1 := ‹sgn (n——m) ≃ -1›
    have : ord_sgn (compare n m) ≃ ord_sgn Ordering.lt := this
    have : compare n m = Ordering.lt := ord_sgn_inject this
    have : n < m := Natural.compare_lt.mp this
    have : Negative (n——m) := neg_diff_lt.mpr this
    exact this

/--
If two differences have the same sign value, their sum will as well.

**Property intuition**: If we visualize differences as arrows on a number line,
an arrow's length is its magnitude and its direction is its sign. Two positive
or two negative numbers will have their arrows pointing in the same direction;
adding them produces a longer arrow, again pointing in the same direction.

**Proof intuition**: Convert statements about signs of differences into
comparisons of their underlying natural numbers. Use the property that adding
pairs of natural numbers that are ordered in the same way produces a pair with
the same ordering.
-/
theorem add_preserves_sign
    {s a b : Difference ℕ} : sgn a ≃ s → sgn b ≃ s → sgn (a + b) ≃ s
    := by
  revert a; intro (n——m); revert b; intro (k——j)
  intro (_ : sgn (n——m) ≃ s) (_ : sgn (k——j) ≃ s)
  show sgn (n——m + k——j) ≃ s
  have : ord_sgn (compare n m) ≃ s := ‹sgn (n——m) ≃ s›
  have : ord_sgn (compare k j) ≃ s := ‹sgn (k——j) ≃ s›
  have : ord_sgn (compare n m) ≃ ord_sgn (compare k j) :=
    Rel.trans ‹ord_sgn (compare n m) ≃ s› (Rel.symm this)
  have : compare n m = compare k j := ord_sgn_inject this
  have : compare (n + k) (m + j) = compare k j :=
    Natural.add_preserves_compare this rfl
  have : sgn (n——m + k——j) ≃ s := calc
    sgn (n——m + k——j)                 ≃ _ := Rel.refl
    sgn ((n + k)——(m + j))            ≃ _ := Rel.refl
    ord_sgn (compare (n + k) (m + j)) ≃ _ := ord_sgn_subst this
    ord_sgn (compare k j)             ≃ _ := ‹ord_sgn (compare k j) ≃ s›
    s                                 ≃ _ := Rel.refl
  exact this

instance sign_props : Sign.Props (Difference ℕ) := {
  positive_iff_sign_pos1 := Generic.positive_iff_sign_pos1
  negative_iff_sign_neg1 := Generic.negative_iff_sign_neg1
  nonzero_iff_nonzero_impl := Generic.nonzero_iff_nonzero_impl
  sign_trichotomy := sign_trichotomy
}

instance sgn_ops : Sgn.Ops (ℤ := Difference ℕ) (Difference ℕ) := {
  sgn := sgn
}

instance sgn_props : Sgn.Props (Difference ℕ) := {
  sgn_zero := sgn_zero
  sgn_positive := sgn_positive
  sgn_negative := sgn_negative
  add_preserves_sign := add_preserves_sign
}

instance sign : Sign (Difference ℕ) := {
  toSignedOps := Generic.signed_ops
  toSignProps := sign_props
  toSgnOps := sgn_ops
  toSgnProps := sgn_props
}

end Lean4Axiomatic.Integer.Impl.Difference