import tactic
import graph_theory.path_embedding graph_theory.contraction
open function

namespace simple_graph
open walk classical

universe u
variables {V V' V'' : Type u}
variables {G H : simple_graph V} {G' : simple_graph V'} {G'' : simple_graph V''}

def is_minor (G : simple_graph V) (G' : simple_graph V') : Prop :=
∃ {V'' : Type u} (G'' : simple_graph V''), G ≼c G'' ∧ G'' ≼s G'

def is_forbidden (H : simple_graph V) (G : simple_graph V') := ¬ (is_minor H G)

infix ` ≼ `:50 := is_minor
infix ` ⋠ `:50 := is_forbidden

namespace is_minor

lemma of_iso : G ≃g G' → G ≼ G' :=
λ φ, ⟨V', G', is_contraction.of_iso φ, by refl⟩

lemma iso_left : G ≃g G' -> G' ≼ G'' -> G ≼ G'' :=
λ h₁ ⟨U,H,h₂,h₃⟩, ⟨_,_,h₂.iso_left h₁,h₃⟩

lemma le_left : G ≤ H -> H ≼ G' -> G ≼ G' :=
begin
  rintro h₁ ⟨U,H',h₂,h₃⟩,
  obtain ⟨H'',h₄,h₅⟩ := h₂.le_left h₁,
  exact ⟨_,_,h₄,h₃.le_left h₅⟩
end

lemma select_left {P : V → Prop} : G ≼ G' -> select P G ≼ G' :=
begin
  rintro ⟨U,H',h₂,h₃⟩,
  obtain ⟨P,h₄⟩ := h₂.select_left,
  exact ⟨_,_,h₄,h₃.select_left⟩
end

lemma smaller_left : G ≼s G' -> G' ≼ G'' -> G ≼ G'' :=
begin
  rintro ⟨f₁,h₁⟩ h₂,
  let H := embed f₁ G,
  let H' := select (set.range f₁) G',
  have h₃ : H' ≼ G'' := select_left h₂,
  have h₄ : H ≼ G'' := le_left (embed.le_select h₁) h₃,
  exact iso_left (embed.iso h₁) h₄
end

lemma contract_left : G ≼c G' -> G' ≼ G'' -> G ≼ G'' :=
λ h₁ ⟨U,H,h₂,h₃⟩, ⟨_,_,h₁.trans h₂,h₃⟩

@[refl] lemma refl : G ≼ G
:= ⟨_,G,is_contraction.refl,is_smaller.refl⟩

@[trans] lemma trans : G ≼ G' -> G' ≼ G'' -> G ≼ G'' :=
λ ⟨U,H,h1,h2⟩ h3, is_minor.contract_left h1 (is_minor.smaller_left h2 h3)

end is_minor
end simple_graph


/-
Copyright (c) 2021 Andrew Yang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Andrew Yang

! This file was ported from Lean 3 source module algebraic_geometry.properties
! leanprover-community/mathlib commit d39590fc8728fbf6743249802486f8c91ffe07bc
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.AlgebraicGeometry.AffineScheme
import Mathbin.RingTheory.Nilpotent
import Mathbin.Topology.Sheaves.SheafCondition.Sites
import Mathbin.Algebra.Category.Ring.Constructions
import Mathbin.RingTheory.LocalProperties

/-!
# Basic properties of schemes

We provide some basic properties of schemes

## Main definition
* `algebraic_geometry.is_integral`: A scheme is integral if it is nontrivial and all nontrivial
  components of the structure sheaf are integral domains.
* `algebraic_geometry.is_reduced`: A scheme is reduced if all the components of the structure sheaf
  is reduced.
-/

open TopologicalSpace Opposite CategoryTheory CategoryTheory.Limits TopCat

namespace AlgebraicGeometry

variable (X : Scheme)

instance : T0Space X.carrier :=
  by
  refine' T0Space.of_open_cover fun x => _
  obtain ⟨U, R, ⟨e⟩⟩ := X.local_affine x
  let e' : U.1 ≃ₜ PrimeSpectrum R :=
    homeo_of_iso ((LocallyRingedSpace.forget_to_SheafedSpace ⋙ SheafedSpace.forget _).mapIso e)
  exact ⟨U.1.1, U.2, U.1.2, e'.embedding.t0_space⟩

instance : QuasiSober X.carrier :=
  by
  apply (config := { instances := false })
    quasiSober_of_open_cover (Set.range fun x => Set.range <| (X.affine_cover.map x).1.base)
  · rintro ⟨_, i, rfl⟩
    exact (X.affine_cover.is_open i).base_open.open_range
  · rintro ⟨_, i, rfl⟩
    exact
      @OpenEmbedding.quasiSober _ _ _
        (Homeomorph.ofEmbedding _
              (X.affine_cover.is_open i).base_open.toEmbedding).symm.OpenEmbedding
        PrimeSpectrum.quasiSober
  · rw [Set.top_eq_univ, Set.unionₛ_range, Set.eq_univ_iff_forall]
    intro x
    exact ⟨_, ⟨_, rfl⟩, X.affine_cover.covers x⟩

/-- A scheme `X` is reduced if all `𝒪ₓ(U)` are reduced. -/
class IsReduced : Prop where
  component_reduced : ∀ U, IsReduced (X.Presheaf.obj (op U)) := by infer_instance
#align algebraic_geometry.is_reduced AlgebraicGeometry.IsReduced

attribute [instance] is_reduced.component_reduced

theorem isReduced_of_stalk_isReduced [∀ x : X.carrier, IsReduced (X.Presheaf.stalk x)] :
    IsReduced X := by
  refine' ⟨fun U => ⟨fun s hs => _⟩⟩
  apply presheaf.section_ext X.sheaf U s 0
  intro x
  rw [RingHom.map_zero]
  change X.presheaf.germ x s = 0
  exact (hs.map _).eq_zero
#align algebraic_geometry.is_reduced_of_stalk_is_reduced AlgebraicGeometry.isReduced_of_stalk_isReduced

instance stalk_isReduced_of_reduced [IsReduced X] (x : X.carrier) :
    IsReduced (X.Presheaf.stalk x) := by
  constructor
  rintro g ⟨n, e⟩
  obtain ⟨U, hxU, s, rfl⟩ := X.presheaf.germ_exist x g
  rw [← map_pow, ← map_zero (X.presheaf.germ ⟨x, hxU⟩)] at e
  obtain ⟨V, hxV, iU, iV, e'⟩ := X.presheaf.germ_eq x hxU hxU _ 0 e
  rw [map_pow, map_zero] at e'
  replace e' := (IsNilpotent.mk _ _ e').eq_zero
  erw [← concrete_category.congr_hom (X.presheaf.germ_res iU ⟨x, hxV⟩) s]
  rw [comp_apply, e', map_zero]
#align algebraic_geometry.stalk_is_reduced_of_reduced AlgebraicGeometry.stalk_isReduced_of_reduced

theorem isReduced_of_open_immersion {X Y : Scheme} (f : X ⟶ Y) [H : IsOpenImmersion f]
    [IsReduced Y] : IsReduced X := by
  constructor
  intro U
  have : U = (opens.map f.1.base).obj (H.base_open.is_open_map.functor.obj U) :=
    by
    ext1
    exact (Set.preimage_image_eq _ H.base_open.inj).symm
  rw [this]
  exact
    isReduced_of_injective (inv <| f.1.c.app (op <| H.base_open.is_open_map.functor.obj U))
      (as_iso <| f.1.c.app (op <| H.base_open.is_open_map.functor.obj U) :
              Y.presheaf.obj _ ≅ _).symm.commRingIsoToRingEquiv.Injective
#align algebraic_geometry.is_reduced_of_open_immersion AlgebraicGeometry.isReduced_of_open_immersion

instance {R : CommRingCat} [H : IsReduced R] : IsReduced (Scheme.spec.obj <| op R) :=
  by
  apply (config := { instances := false }) is_reduced_of_stalk_is_reduced
  intro x
  dsimp
  have : _root_.is_reduced (CommRingCat.of <| Localization.AtPrime (PrimeSpectrum.asIdeal x)) :=
    by
    dsimp
    infer_instance
  exact
    isReduced_of_injective (structure_sheaf.stalk_iso R x).Hom
      (structure_sheaf.stalk_iso R x).commRingIsoToRingEquiv.Injective

theorem affine_isReduced_iff (R : CommRingCat) :
    IsReduced (Scheme.spec.obj <| op R) ↔ IsReduced R :=
  by
  refine' ⟨_, fun h => inferInstance⟩
  intro h
  skip
  have :
    _root_.is_reduced (LocallyRingedSpace.Γ.obj (op <| Spec.to_LocallyRingedSpace.obj <| op R)) :=
    by
    change _root_.is_reduced ((Scheme.Spec.obj <| op R).Presheaf.obj <| op ⊤)
    infer_instance
  exact
    isReduced_of_injective (to_Spec_Γ R) (as_iso <| to_Spec_Γ R).commRingIsoToRingEquiv.Injective
#align algebraic_geometry.affine_is_reduced_iff AlgebraicGeometry.affine_isReduced_iff

theorem isReduced_of_isAffine_isReduced [IsAffine X] [h : IsReduced (X.Presheaf.obj (op ⊤))] :
    IsReduced X :=
  haveI : IsReduced (Scheme.Spec.obj (op (Scheme.Γ.obj (op X)))) :=
    by
    rw [affine_is_reduced_iff]
    exact h
  is_reduced_of_open_immersion X.iso_Spec.hom
#align algebraic_geometry.is_reduced_of_is_affine_is_reduced AlgebraicGeometry.isReduced_of_isAffine_isReduced

/-- To show that a statement `P` holds for all open subsets of all schemes, it suffices to show that
1. In any scheme `X`, if `P` holds for an open cover of `U`, then `P` holds for `U`.
2. For an open immerison `f : X ⟶ Y`, if `P` holds for the entire space of `X`, then `P` holds for
  the image of `f`.
3. `P` holds for the entire space of an affine scheme.
-/
theorem reduce_to_affine_global (P : ∀ (X : Scheme) (U : Opens X.carrier), Prop)
    (h₁ :
      ∀ (X : Scheme) (U : Opens X.carrier),
        (∀ x : U, ∃ (V : _)(h : x.1 ∈ V)(i : V ⟶ U), P X V) → P X U)
    (h₂ :
      ∀ {X Y} (f : X ⟶ Y) [hf : IsOpenImmersion f],
        ∃ (U : Set X.carrier)(V : Set Y.carrier)(hU : U = ⊤)(hV : V = Set.range f.1.base),
          P X ⟨U, hU.symm ▸ isOpen_univ⟩ → P Y ⟨V, hV.symm ▸ hf.base_open.open_range⟩)
    (h₃ : ∀ R : CommRingCat, P (Scheme.spec.obj <| op R) ⊤) :
    ∀ (X : Scheme) (U : Opens X.carrier), P X U :=
  by
  intro X U
  apply h₁
  intro x
  obtain ⟨_, ⟨j, rfl⟩, hx, i⟩ :=
    X.affine_basis_cover_is_basis.exists_subset_of_mem_open (SetLike.mem_coe.2 x.prop) U.is_open
  let U' : opens _ := ⟨_, (X.affine_basis_cover.is_open j).base_open.open_range⟩
  let i' : U' ⟶ U := hom_of_le i
  refine' ⟨U', hx, i', _⟩
  obtain ⟨_, _, rfl, rfl, h₂'⟩ := h₂ (X.affine_basis_cover.map j)
  apply h₂'
  apply h₃
#align algebraic_geometry.reduce_to_affine_global AlgebraicGeometry.reduce_to_affine_global

theorem reduce_to_affine_nbhd (P : ∀ (X : Scheme) (x : X.carrier), Prop)
    (h₁ : ∀ (R : CommRingCat) (x : PrimeSpectrum R), P (Scheme.spec.obj <| op R) x)
    (h₂ : ∀ {X Y} (f : X ⟶ Y) [IsOpenImmersion f] (x : X.carrier), P X x → P Y (f.1.base x)) :
    ∀ (X : Scheme) (x : X.carrier), P X x := by
  intro X x
  obtain ⟨y, e⟩ := X.affine_cover.covers x
  convert h₂ (X.affine_cover.map (X.affine_cover.f x)) y _
  · rw [e]
  apply h₁
#align algebraic_geometry.reduce_to_affine_nbhd AlgebraicGeometry.reduce_to_affine_nbhd

theorem eq_zero_of_basicOpen_eq_bot {X : Scheme} [hX : IsReduced X] {U : Opens X.carrier}
    (s : X.Presheaf.obj (op U)) (hs : X.basicOpen s = ⊥) : s = 0 :=
  by
  apply TopCat.Presheaf.section_ext X.sheaf U
  simp_rw [RingHom.map_zero]
  revert X U hX s
  refine' reduce_to_affine_global _ _ _ _
  · intro X U hx hX s hs x
    obtain ⟨V, hx, i, H⟩ := hx x
    specialize H (X.presheaf.map i.op s)
    erw [Scheme.basic_open_res] at H
    rw [hs] at H
    specialize H inf_bot_eq ⟨x, hx⟩
    erw [TopCat.Presheaf.germ_res_apply] at H
    exact H
  · rintro X Y f hf
    have e : f.val.base ⁻¹' Set.range ⇑f.val.base = Set.univ := by
      rw [← Set.image_univ, Set.preimage_image_eq _ hf.base_open.inj]
    refine' ⟨_, _, e, rfl, _⟩
    rintro H hX s hs ⟨_, x, rfl⟩
    haveI := is_reduced_of_open_immersion f
    specialize
      H (f.1.c.app _ s) _
        ⟨x, by
          rw [opens.mem_mk, e]
          trivial⟩
    · rw [← Scheme.preimage_basic_open, hs]
      ext1
      simp [opens.map]
    · erw [← PresheafedSpace.stalk_map_germ_apply f.1 ⟨_, _⟩ ⟨x, _⟩] at H
      apply_fun inv <| PresheafedSpace.stalk_map f.val x  at H
      erw [CategoryTheory.IsIso.hom_inv_id_apply, map_zero] at H
      exact H
  · intro R hX s hs x
    erw [basic_open_eq_of_affine', PrimeSpectrum.basicOpen_eq_bot_iff] at hs
    replace hs := hs.map (Spec_Γ_identity.app R).inv
    -- what the hell?!
    replace hs := @IsNilpotent.eq_zero _ _ _ _ (show _ from _) hs
    rw [iso.hom_inv_id_apply] at hs
    rw [hs, map_zero]
    exact @is_reduced.component_reduced hX ⊤
#align algebraic_geometry.eq_zero_of_basic_open_eq_bot AlgebraicGeometry.eq_zero_of_basicOpen_eq_bot

@[simp]
theorem basicOpen_eq_bot_iff {X : Scheme} [IsReduced X] {U : Opens X.carrier}
    (s : X.Presheaf.obj <| op U) : X.basicOpen s = ⊥ ↔ s = 0 :=
  by
  refine' ⟨eq_zero_of_basic_open_eq_bot s, _⟩
  rintro rfl
  simp
#align algebraic_geometry.basic_open_eq_bot_iff AlgebraicGeometry.basicOpen_eq_bot_iff

/-- A scheme `X` is integral if its carrier is nonempty,
and `𝒪ₓ(U)` is an integral domain for each `U ≠ ∅`. -/
class IsIntegral : Prop where
  Nonempty : Nonempty X.carrier := by infer_instance
  component_integral : ∀ (U : Opens X.carrier) [Nonempty U], IsDomain (X.Presheaf.obj (op U)) := by
    infer_instance
#align algebraic_geometry.is_integral AlgebraicGeometry.IsIntegral

attribute [instance] is_integral.component_integral is_integral.nonempty

instance [h : IsIntegral X] : IsDomain (X.Presheaf.obj (op ⊤)) :=
  @IsIntegral.component_integral _ _ (by simp)

instance (priority := 900) isReduced_of_isIntegral [IsIntegral X] : IsReduced X :=
  by
  constructor
  intro U
  cases' U.1.eq_empty_or_nonempty with h h
  · have : U = ⊥ := SetLike.ext' h
    haveI := CommRingCat.subsingleton_of_isTerminal (X.sheaf.is_terminal_of_eq_empty this)
    change _root_.is_reduced (X.sheaf.val.obj (op U))
    infer_instance
  · haveI : Nonempty U := by simpa
    infer_instance
#align algebraic_geometry.is_reduced_of_is_integral AlgebraicGeometry.isReduced_of_isIntegral

instance is_irreducible_of_isIntegral [IsIntegral X] : IrreducibleSpace X.carrier :=
  by
  by_contra H
  replace H : ¬IsPreirreducible (⊤ : Set X.carrier) := fun h =>
    H { to_preirreducibleSpace := ⟨h⟩
        to_nonempty := inferInstance }
  simp_rw [isPreirreducible_iff_closed_union_closed, not_forall, not_or] at H
  rcases H with ⟨S, T, hS, hT, h₁, h₂, h₃⟩
  erw [not_forall] at h₂ h₃
  simp_rw [not_forall] at h₂ h₃
  haveI : Nonempty (⟨Sᶜ, hS.1⟩ : opens X.carrier) := ⟨⟨_, h₂.some_spec.some_spec⟩⟩
  haveI : Nonempty (⟨Tᶜ, hT.1⟩ : opens X.carrier) := ⟨⟨_, h₃.some_spec.some_spec⟩⟩
  haveI : Nonempty (⟨Sᶜ, hS.1⟩ ⊔ ⟨Tᶜ, hT.1⟩ : opens X.carrier) :=
    ⟨⟨_, Or.inl h₂.some_spec.some_spec⟩⟩
  let e : X.presheaf.obj _ ≅ CommRingCat.of _ :=
    (X.sheaf.is_product_of_disjoint ⟨_, hS.1⟩ ⟨_, hT.1⟩ _).conePointUniqueUpToIso
      (CommRingCat.prodFanIsLimit _ _)
  apply (config := { instances := false }) false_of_nontrivial_of_product_domain
  · exact e.symm.CommRing_iso_to_ring_equiv.is_domain _
  · apply X.to_LocallyRingedSpace.component_nontrivial
  · apply X.to_LocallyRingedSpace.component_nontrivial
  · ext x
    constructor
    · rintro ⟨hS, hT⟩
      cases h₁ (show x ∈ ⊤ by trivial)
      exacts[hS h, hT h]
    · intro x
      exact x.rec _
#align algebraic_geometry.is_irreducible_of_is_integral AlgebraicGeometry.is_irreducible_of_isIntegral

theorem isIntegral_of_is_irreducible_isReduced [IsReduced X] [H : IrreducibleSpace X.carrier] :
    IsIntegral X := by
  constructor
  intro U hU
  haveI := (@LocallyRingedSpace.component_nontrivial X.to_LocallyRingedSpace U hU).1
  have :
    NoZeroDivisors
      (X.to_LocallyRingedSpace.to_SheafedSpace.to_PresheafedSpace.presheaf.obj (op U)) :=
    by
    refine' ⟨fun a b e => _⟩
    simp_rw [← basic_open_eq_bot_iff, ← opens.not_nonempty_iff_eq_bot]
    by_contra' h
    obtain ⟨_, ⟨x, hx₁, rfl⟩, ⟨x, hx₂, e'⟩⟩ :=
      @nonempty_preirreducible_inter _ H.1 (X.basic_open a).2 (X.basic_open b).2 h.1 h.2
    replace e' := Subtype.eq e'
    subst e'
    replace e := congr_arg (X.presheaf.germ x) e
    rw [RingHom.map_mul, RingHom.map_zero] at e
    refine' zero_ne_one' (X.presheaf.stalk x.1) (isUnit_zero_iff.1 _)
    convert hx₁.mul hx₂
    exact e.symm
  exact NoZeroDivisors.to_isDomain _
#align algebraic_geometry.is_integral_of_is_irreducible_is_reduced AlgebraicGeometry.isIntegral_of_is_irreducible_isReduced

theorem isIntegral_iff_is_irreducible_and_isReduced :
    IsIntegral X ↔ IrreducibleSpace X.carrier ∧ IsReduced X :=
  ⟨fun _ => ⟨inferInstance, inferInstance⟩, fun ⟨_, _⟩ =>
    is_integral_of_is_irreducible_is_reduced X⟩
#align algebraic_geometry.is_integral_iff_is_irreducible_and_is_reduced AlgebraicGeometry.isIntegral_iff_is_irreducible_and_isReduced

theorem isIntegral_of_open_immersion {X Y : Scheme} (f : X ⟶ Y) [H : IsOpenImmersion f]
    [IsIntegral Y] [Nonempty X.carrier] : IsIntegral X :=
  by
  constructor
  intro U hU
  have : U = (opens.map f.1.base).obj (H.base_open.is_open_map.functor.obj U) :=
    by
    ext1
    exact (Set.preimage_image_eq _ H.base_open.inj).symm
  rw [this]
  have : IsDomain (Y.presheaf.obj (op (H.base_open.is_open_map.functor.obj U))) :=
    by
    apply (config := { instances := false }) is_integral.component_integral
    infer_instance
    refine' ⟨⟨_, _, hU.some.prop, rfl⟩⟩
  exact
    (as_iso <| f.1.c.app (op <| H.base_open.is_open_map.functor.obj U) :
              Y.presheaf.obj _ ≅ _).symm.commRingIsoToRingEquiv.IsDomain
      _
#align algebraic_geometry.is_integral_of_open_immersion AlgebraicGeometry.isIntegral_of_open_immersion

instance {R : CommRingCat} [H : IsDomain R] : IsIntegral (Scheme.spec.obj <| op R) :=
  by
  apply (config := { instances := false }) is_integral_of_is_irreducible_is_reduced
  · infer_instance
  · dsimp [Spec.Top_obj]
    infer_instance

theorem affine_isIntegral_iff (R : CommRingCat) :
    IsIntegral (Scheme.spec.obj <| op R) ↔ IsDomain R :=
  ⟨fun h =>
    RingEquiv.isDomain ((Scheme.Spec.obj <| op R).Presheaf.obj _)
      (as_iso <| to_Spec_Γ R).commRingIsoToRingEquiv,
    fun h => inferInstance⟩
#align algebraic_geometry.affine_is_integral_iff AlgebraicGeometry.affine_isIntegral_iff

theorem isIntegral_of_isAffine_isDomain [IsAffine X] [Nonempty X.carrier]
    [h : IsDomain (X.Presheaf.obj (op ⊤))] : IsIntegral X :=
  haveI : IsIntegral (Scheme.Spec.obj (op (Scheme.Γ.obj (op X)))) :=
    by
    rw [affine_is_integral_iff]
    exact h
  is_integral_of_open_immersion X.iso_Spec.hom
#align algebraic_geometry.is_integral_of_is_affine_is_domain AlgebraicGeometry.isIntegral_of_isAffine_isDomain

theorem map_injective_of_isIntegral [IsIntegral X] {U V : Opens X.carrier} (i : U ⟶ V)
    [H : Nonempty U] : Function.Injective (X.Presheaf.map i.op) :=
  by
  rw [injective_iff_map_eq_zero]
  intro x hx
  rw [← basic_open_eq_bot_iff] at hx⊢
  rw [Scheme.basic_open_res] at hx
  revert hx
  contrapose!
  simp_rw [← opens.not_nonempty_iff_eq_bot, Classical.not_not]
  apply nonempty_preirreducible_inter U.is_open (RingedSpace.basic_open _ _).IsOpen
  simpa using H
#align algebraic_geometry.map_injective_of_is_integral AlgebraicGeometry.map_injective_of_isIntegral

end AlgebraicGeometry


/-
Copyright (c) 2018 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import category_theory.limits.has_limits
import category_theory.discrete_category

/-!
# Categorical (co)products

This file defines (co)products as special cases of (co)limits.

A product is the categorical generalization of the object `Π i, f i` where `f : ι → C`. It is a
limit cone over the diagram formed by `f`, implemented by converting `f` into a functor
`discrete ι ⥤ C`.

A coproduct is the dual concept.

## Main definitions

* a `fan` is a cone over a discrete category
* `fan.mk` constructs a fan from an indexed collection of maps
* a `pi` is a `limit (discrete.functor f)`

Each of these has a dual.

## Implementation notes
As with the other special shapes in the limits library, all the definitions here are given as
`abbreviation`s of the general statements for limits, so all the `simp` lemmas and theorems about
general limits can be used.
-/

noncomputable theory

universes w v u u₂

open category_theory

namespace category_theory.limits

variables {β : Type w}
variables {C : Type u} [category.{v} C]

-- We don't need an analogue of `pair` (for binary products), `parallel_pair` (for equalizers),
-- or `(co)span`, since we already have `discrete.functor`.

local attribute [tidy] tactic.discrete_cases

/-- A fan over `f : β → C` consists of a collection of maps from an object `P` to every `f b`. -/
abbreviation fan (f : β → C) := cone (discrete.functor f)
/-- A cofan over `f : β → C` consists of a collection of maps from every `f b` to an object `P`. -/
abbreviation cofan (f : β → C) := cocone (discrete.functor f)

/-- A fan over `f : β → C` consists of a collection of maps from an object `P` to every `f b`. -/
@[simps]
def fan.mk {f : β → C} (P : C) (p : Π b, P ⟶ f b) : fan f :=
{ X := P,
  π := { app := λ X, p X.as } }

/-- A cofan over `f : β → C` consists of a collection of maps from every `f b` to an object `P`. -/
@[simps]
def cofan.mk {f : β → C} (P : C) (p : Π b, f b ⟶ P) : cofan f :=
{ X := P,
  ι := { app := λ X, p X.as } }

-- FIXME dualize as needed below (and rename?)

/-- Get the `j`th map in the fan -/
def fan.proj  {f : β → C} (p : fan f) (j : β) : p.X ⟶ f j := p.π.app (discrete.mk j)
@[simp] lemma fan_mk_proj {f : β → C} (P : C) (p : Π b, P ⟶ f b) (j : β) :
  (fan.mk P p).proj j = p j := rfl

/-- An abbreviation for `has_limit (discrete.functor f)`. -/
abbreviation has_product (f : β → C) := has_limit (discrete.functor f)

/-- An abbreviation for `has_colimit (discrete.functor f)`. -/
abbreviation has_coproduct (f : β → C) := has_colimit (discrete.functor f)

/-- Make a fan `f` into a limit fan by providing `lift`, `fac`, and `uniq` --
  just a convenience lemma to avoid having to go through `discrete` -/
@[simps] def mk_fan_limit {f : β → C} (t : fan f)
  (lift : Π s : fan f, s.X ⟶ t.X)
  (fac : ∀ (s : fan f) (j : β), lift s ≫ (t.proj j) = s.proj j)
  (uniq : ∀ (s : fan f) (m : s.X ⟶ t.X) (w : ∀ j : β, m ≫ t.proj j = s.proj j), m = lift s) :
  is_limit t :=
{ lift := lift,
  fac' := λ s j, by convert fac s j.as; simp,
  uniq' := λ s m w, uniq s m (λ j, w (discrete.mk j)), }

section
variables (C)

/-- An abbreviation for `has_limits_of_shape (discrete f)`. -/
abbreviation has_products_of_shape (β : Type v) := has_limits_of_shape.{v} (discrete β)
/-- An abbreviation for `has_colimits_of_shape (discrete f)`. -/
abbreviation has_coproducts_of_shape (β : Type v) := has_colimits_of_shape.{v} (discrete β)
end

/-- `pi_obj f` computes the product of a family of elements `f`.
(It is defined as an abbreviation for `limit (discrete.functor f)`,
so for most facts about `pi_obj f`, you will just use general facts about limits.) -/
abbreviation pi_obj (f : β → C) [has_product f] := limit (discrete.functor f)
/-- `sigma_obj f` computes the coproduct of a family of elements `f`.
(It is defined as an abbreviation for `colimit (discrete.functor f)`,
so for most facts about `sigma_obj f`, you will just use general facts about colimits.) -/
abbreviation sigma_obj (f : β → C) [has_coproduct f] := colimit (discrete.functor f)

notation `∏ ` f:20 := pi_obj f
notation `∐ ` f:20 := sigma_obj f

/-- The `b`-th projection from the pi object over `f` has the form `∏ f ⟶ f b`. -/
abbreviation pi.π (f : β → C) [has_product f] (b : β) : ∏ f ⟶ f b :=
limit.π (discrete.functor f) (discrete.mk b)
/-- The `b`-th inclusion into the sigma object over `f` has the form `f b ⟶ ∐ f`. -/
abbreviation sigma.ι (f : β → C) [has_coproduct f] (b : β) : f b ⟶ ∐ f :=
colimit.ι (discrete.functor f) (discrete.mk b)

/-- The fan constructed of the projections from the product is limiting. -/
def product_is_product (f : β → C) [has_product f] :
  is_limit (fan.mk _ (pi.π f)) :=
is_limit.of_iso_limit (limit.is_limit (discrete.functor f)) (cones.ext (iso.refl _) (by tidy))

/-- The cofan constructed of the inclusions from the coproduct is colimiting. -/
def coproduct_is_coproduct (f : β → C) [has_coproduct f] :
  is_colimit (cofan.mk _ (sigma.ι f)) :=
is_colimit.of_iso_colimit (colimit.is_colimit (discrete.functor f)) (cocones.ext (iso.refl _)
  (by tidy))

/-- A collection of morphisms `P ⟶ f b` induces a morphism `P ⟶ ∏ f`. -/
abbreviation pi.lift {f : β → C} [has_product f] {P : C} (p : Π b, P ⟶ f b) : P ⟶ ∏ f :=
limit.lift _ (fan.mk P p)
/-- A collection of morphisms `f b ⟶ P` induces a morphism `∐ f ⟶ P`. -/
abbreviation sigma.desc {f : β → C} [has_coproduct f] {P : C} (p : Π b, f b ⟶ P) : ∐ f ⟶ P :=
colimit.desc _ (cofan.mk P p)

/--
Construct a morphism between categorical products (indexed by the same type)
from a family of morphisms between the factors.
-/
abbreviation pi.map {f g : β → C} [has_product f] [has_product g]
  (p : Π b, f b ⟶ g b) : ∏ f ⟶ ∏ g :=
lim_map (discrete.nat_trans (λ X, p X.as))
/--
Construct an isomorphism between categorical products (indexed by the same type)
from a family of isomorphisms between the factors.
-/
abbreviation pi.map_iso {f g : β → C} [has_products_of_shape β C]
  (p : Π b, f b ≅ g b) : ∏ f ≅ ∏ g :=
lim.map_iso (discrete.nat_iso (λ X, p X.as))
/--
Construct a morphism between categorical coproducts (indexed by the same type)
from a family of morphisms between the factors.
-/
abbreviation sigma.map {f g : β → C} [has_coproduct f] [has_coproduct g]
  (p : Π b, f b ⟶ g b) : ∐ f ⟶ ∐ g :=
colim_map (discrete.nat_trans (λ X, p X.as))
/--
Construct an isomorphism between categorical coproducts (indexed by the same type)
from a family of isomorphisms between the factors.
-/
abbreviation sigma.map_iso {f g : β → C} [has_coproducts_of_shape β C]
  (p : Π b, f b ≅ g b) : ∐ f ≅ ∐ g :=
colim.map_iso (discrete.nat_iso (λ X, p X.as))

section comparison

variables {D : Type u₂} [category.{v} D] (G : C ⥤ D)
variables (f : β → C)

/-- The comparison morphism for the product of `f`. This is an iso iff `G` preserves the product
of `f`, see `preserves_product.of_iso_comparison`. -/
def pi_comparison [has_product f] [has_product (λ b, G.obj (f b))] :
  G.obj (∏ f) ⟶ ∏ (λ b, G.obj (f b)) :=
pi.lift (λ b, G.map (pi.π f b))

@[simp, reassoc]
lemma pi_comparison_comp_π [has_product f] [has_product (λ b, G.obj (f b))] (b : β) :
  pi_comparison G f ≫ pi.π _ b = G.map (pi.π f b) :=
limit.lift_π _ (discrete.mk b)

@[simp, reassoc]
lemma map_lift_pi_comparison [has_product f] [has_product (λ b, G.obj (f b))]
  (P : C) (g : Π j, P ⟶ f j) :
  G.map (pi.lift g) ≫ pi_comparison G f = pi.lift (λ j, G.map (g j)) :=
by { ext, discrete_cases, simp [← G.map_comp] }

/-- The comparison morphism for the coproduct of `f`. This is an iso iff `G` preserves the coproduct
of `f`, see `preserves_coproduct.of_iso_comparison`. -/
def sigma_comparison [has_coproduct f] [has_coproduct (λ b, G.obj (f b))] :
  ∐ (λ b, G.obj (f b)) ⟶ G.obj (∐ f) :=
sigma.desc (λ b, G.map (sigma.ι f b))

@[simp, reassoc]
lemma ι_comp_sigma_comparison [has_coproduct f] [has_coproduct (λ b, G.obj (f b))] (b : β) :
  sigma.ι _ b ≫ sigma_comparison G f = G.map (sigma.ι f b) :=
colimit.ι_desc _ (discrete.mk b)

@[simp, reassoc]
lemma sigma_comparison_map_desc [has_coproduct f] [has_coproduct (λ b, G.obj (f b))]
  (P : C) (g : Π j, f j ⟶ P) :
  sigma_comparison G f ≫ G.map (sigma.desc g) = sigma.desc (λ j, G.map (g j)) :=
by { ext, discrete_cases, simp [← G.map_comp] }

end comparison

variables (C)

/-- An abbreviation for `Π J, has_limits_of_shape (discrete J) C` -/
abbreviation has_products := Π (J : Type v), has_limits_of_shape (discrete J) C
/-- An abbreviation for `Π J, has_colimits_of_shape (discrete J) C` -/
abbreviation has_coproducts := Π (J : Type v), has_colimits_of_shape (discrete J) C

variable {C}

lemma has_products_of_limit_fans (lf : ∀ {J : Type v} (f : J → C), fan f)
  (lf_is_limit : ∀ {J : Type v} (f : J → C), is_limit (lf f)) : has_products C :=
λ J, { has_limit := λ F, has_limit.mk
  ⟨(cones.postcompose discrete.nat_iso_functor.inv).obj (lf (λ j, F.obj (discrete.mk j))),
    (is_limit.postcompose_inv_equiv _ _).symm (lf_is_limit _)⟩ }

/-!
(Co)products over a type with a unique term.
-/
section unique
variables {C} [unique β] (f : β → C)

/-- The limit cone for the product over an index type with exactly one term. -/
@[simps]
def limit_cone_of_unique : limit_cone (discrete.functor f) :=
{ cone :=
  { X := f default,
    π := { app := λ j, eq_to_hom (by { dsimp, congr, }), }, },
  is_limit :=
  { lift := λ s, s.π.app default,
    fac' := λ s j, begin
      have w := (s.π.naturality (eq_to_hom (unique.default_eq _))).symm,
      dsimp at w,
      simpa [eq_to_hom_map] using w,
    end,
    uniq' := λ s m w, begin
      specialize w default,
      dsimp at w,
      simpa using w,
    end, }, }

@[priority 100] instance has_product_unique : has_product f :=
has_limit.mk (limit_cone_of_unique f)

/-- A product over a index type with exactly one term is just the object over that term. -/
@[simps]
def product_unique_iso : ∏ f ≅ f default :=
is_limit.cone_point_unique_up_to_iso (limit.is_limit _) (limit_cone_of_unique f).is_limit

/-- The colimit cocone for the coproduct over an index type with exactly one term. -/
@[simps]
def colimit_cocone_of_unique : colimit_cocone (discrete.functor f) :=
{ cocone :=
  { X := f default,
    ι := { app := λ j, eq_to_hom (by { discrete_cases, dsimp, congr, }), }, },
  is_colimit :=
  { desc := λ s, s.ι.app default,
    fac' := λ s j, begin
      have w := (s.ι.naturality (eq_to_hom (unique.eq_default _))),
      dsimp at w,
      simpa [eq_to_hom_map] using w,
    end,
    uniq' := λ s m w, begin
      specialize w default,
      dsimp at w,
      simpa using w,
    end, }, }

@[priority 100] instance has_coproduct_unique : has_coproduct f :=
has_colimit.mk (colimit_cocone_of_unique f)

/-- A coproduct over a index type with exactly one term is just the object over that term. -/
@[simps]
def coproduct_unique_iso : ∐ f ≅ f default :=
is_colimit.cocone_point_unique_up_to_iso (colimit.is_colimit _)
  (colimit_cocone_of_unique f).is_colimit

end unique

section reindex
variables {C} {γ : Type v} (ε : β ≃ γ) (f : γ → C)

section
variables [has_product f] [has_product (f ∘ ε)]

/-- Reindex a categorical product via an equivalence of the index types. -/
def pi.reindex : pi_obj (f ∘ ε) ≅ pi_obj f :=
has_limit.iso_of_equivalence (discrete.equivalence ε) (discrete.nat_iso (λ i, iso.refl _))

@[simp, reassoc]
lemma pi.reindex_hom_π (b : β) : (pi.reindex ε f).hom ≫ pi.π f (ε b) = pi.π (f ∘ ε) b :=
begin
  dsimp [pi.reindex],
  simp only [has_limit.iso_of_equivalence_hom_π, discrete.nat_iso_inv_app,
    equivalence.equivalence_mk'_counit, discrete.equivalence_counit_iso, discrete.nat_iso_hom_app,
    eq_to_iso.hom, eq_to_hom_map],
  dsimp,
  simpa [eq_to_hom_map] using
    limit.w (discrete.functor (f ∘ ε)) (discrete.eq_to_hom' (ε.symm_apply_apply b)),
end

@[simp, reassoc]
lemma pi.reindex_inv_π (b : β) : (pi.reindex ε f).inv ≫ pi.π (f ∘ ε) b = pi.π f (ε b) :=
by simp [iso.inv_comp_eq]

end

section
variables [has_coproduct f] [has_coproduct (f ∘ ε)]

/-- Reindex a categorical coproduct via an equivalence of the index types. -/
def sigma.reindex : sigma_obj (f ∘ ε) ≅ sigma_obj f :=
has_colimit.iso_of_equivalence (discrete.equivalence ε) (discrete.nat_iso (λ i, iso.refl _))

@[simp, reassoc]
lemma sigma.ι_reindex_hom (b : β) : sigma.ι (f ∘ ε) b ≫ (sigma.reindex ε f).hom = sigma.ι f (ε b) :=
begin
  dsimp [sigma.reindex],
  simp only [has_colimit.iso_of_equivalence_hom_π, equivalence.equivalence_mk'_unit,
    discrete.equivalence_unit_iso, discrete.nat_iso_hom_app, eq_to_iso.hom, eq_to_hom_map,
    discrete.nat_iso_inv_app],
  dsimp,
  simp [eq_to_hom_map,
    ←colimit.w (discrete.functor f) (discrete.eq_to_hom' (ε.apply_symm_apply (ε b)))],
end

@[simp, reassoc]
lemma sigma.ι_reindex_inv (b : β) : sigma.ι f (ε b) ≫ (sigma.reindex ε f).inv = sigma.ι (f ∘ ε) b :=
by simp [iso.comp_inv_eq]

end

end reindex

end category_theory.limits


/-
Copyright (c) 2022 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson

! This file was ported from Lean 3 source module model_theory.finitely_generated
! leanprover-community/mathlib commit 0602c59878ff3d5f71dea69c2d32ccf2e93e5398
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.ModelTheory.Substructures

/-!
# Finitely Generated First-Order Structures
This file defines what it means for a first-order (sub)structure to be finitely or countably
generated, similarly to other finitely-generated objects in the algebra library.

## Main Definitions
* `first_order.language.substructure.fg` indicates that a substructure is finitely generated.
* `first_order.language.Structure.fg` indicates that a structure is finitely generated.
* `first_order.language.substructure.cg` indicates that a substructure is countably generated.
* `first_order.language.Structure.cg` indicates that a structure is countably generated.

## TODO
Develop a more unified definition of finite generation using the theory of closure operators, or use
this definition of finite generation to define the others.

-/

open FirstOrder

open Set

namespace FirstOrder

namespace Language

open Structure

variable {L : Language} {M : Type _} [L.Structure M]

namespace Substructure

/-- A substructure of `M` is finitely generated if it is the closure of a finite subset of `M`. -/
def Fg (N : L.Substructure M) : Prop :=
  ∃ S : Finset M, closure L ↑S = N
#align first_order.language.substructure.fg FirstOrder.Language.Substructure.Fg

theorem fg_def {N : L.Substructure M} : N.Fg ↔ ∃ S : Set M, S.Finite ∧ closure L S = N :=
  ⟨fun ⟨t, h⟩ => ⟨_, Finset.finite_toSet t, h⟩,
    by
    rintro ⟨t', h, rfl⟩
    rcases finite.exists_finset_coe h with ⟨t, rfl⟩
    exact ⟨t, rfl⟩⟩
#align first_order.language.substructure.fg_def FirstOrder.Language.Substructure.fg_def

theorem fg_iff_exists_fin_generating_family {N : L.Substructure M} :
    N.Fg ↔ ∃ (n : ℕ)(s : Fin n → M), closure L (range s) = N :=
  by
  rw [fg_def]
  constructor
  · rintro ⟨S, Sfin, hS⟩
    obtain ⟨n, f, rfl⟩ := Sfin.fin_embedding
    exact ⟨n, f, hS⟩
  · rintro ⟨n, s, hs⟩
    refine' ⟨range s, finite_range s, hs⟩
#align first_order.language.substructure.fg_iff_exists_fin_generating_family FirstOrder.Language.Substructure.fg_iff_exists_fin_generating_family

theorem fg_bot : (⊥ : L.Substructure M).Fg :=
  ⟨∅, by rw [Finset.coe_empty, closure_empty]⟩
#align first_order.language.substructure.fg_bot FirstOrder.Language.Substructure.fg_bot

theorem fg_closure {s : Set M} (hs : s.Finite) : Fg (closure L s) :=
  ⟨hs.toFinset, by rw [hs.coe_to_finset]⟩
#align first_order.language.substructure.fg_closure FirstOrder.Language.Substructure.fg_closure

theorem fg_closure_singleton (x : M) : Fg (closure L ({x} : Set M)) :=
  fg_closure (finite_singleton x)
#align first_order.language.substructure.fg_closure_singleton FirstOrder.Language.Substructure.fg_closure_singleton

theorem Fg.sup {N₁ N₂ : L.Substructure M} (hN₁ : N₁.Fg) (hN₂ : N₂.Fg) : (N₁ ⊔ N₂).Fg :=
  let ⟨t₁, ht₁⟩ := fg_def.1 hN₁
  let ⟨t₂, ht₂⟩ := fg_def.1 hN₂
  fg_def.2 ⟨t₁ ∪ t₂, ht₁.1.union ht₂.1, by rw [closure_union, ht₁.2, ht₂.2]⟩
#align first_order.language.substructure.fg.sup FirstOrder.Language.Substructure.Fg.sup

theorem Fg.map {N : Type _} [L.Structure N] (f : M →[L] N) {s : L.Substructure M} (hs : s.Fg) :
    (s.map f).Fg :=
  let ⟨t, ht⟩ := fg_def.1 hs
  fg_def.2 ⟨f '' t, ht.1.image _, by rw [closure_image, ht.2]⟩
#align first_order.language.substructure.fg.map FirstOrder.Language.Substructure.Fg.map

theorem Fg.of_map_embedding {N : Type _} [L.Structure N] (f : M ↪[L] N) {s : L.Substructure M}
    (hs : (s.map f.toHom).Fg) : s.Fg :=
  by
  rcases hs with ⟨t, h⟩
  rw [fg_def]
  refine' ⟨f ⁻¹' t, t.finite_to_set.preimage (f.injective.inj_on _), _⟩
  have hf : Function.Injective f.to_hom := f.injective
  refine' map_injective_of_injective hf _
  rw [← h, map_closure, embedding.coe_to_hom, image_preimage_eq_of_subset]
  intro x hx
  have h' := subset_closure hx
  rw [h] at h'
  exact hom.map_le_range h'
#align first_order.language.substructure.fg.of_map_embedding FirstOrder.Language.Substructure.Fg.of_map_embedding

/-- A substructure of `M` is countably generated if it is the closure of a countable subset of `M`.
-/
def Cg (N : L.Substructure M) : Prop :=
  ∃ S : Set M, S.Countable ∧ closure L S = N
#align first_order.language.substructure.cg FirstOrder.Language.Substructure.Cg

theorem cg_def {N : L.Substructure M} : N.Cg ↔ ∃ S : Set M, S.Countable ∧ closure L S = N :=
  Iff.refl _
#align first_order.language.substructure.cg_def FirstOrder.Language.Substructure.cg_def

theorem Fg.cg {N : L.Substructure M} (h : N.Fg) : N.Cg :=
  by
  obtain ⟨s, hf, rfl⟩ := fg_def.1 h
  refine' ⟨s, hf.countable, rfl⟩
#align first_order.language.substructure.fg.cg FirstOrder.Language.Substructure.Fg.cg

theorem cg_iff_empty_or_exists_nat_generating_family {N : L.Substructure M} :
    N.Cg ↔ ↑N = (∅ : Set M) ∨ ∃ s : ℕ → M, closure L (range s) = N :=
  by
  rw [cg_def]
  constructor
  · rintro ⟨S, Scount, hS⟩
    cases' eq_empty_or_nonempty ↑N with h h
    · exact Or.intro_left _ h
    obtain ⟨f, h'⟩ :=
      (Scount.union (Set.countable_singleton h.some)).exists_eq_range
        (singleton_nonempty h.some).inr
    refine' Or.intro_right _ ⟨f, _⟩
    rw [← h', closure_union, hS, sup_eq_left, closure_le]
    exact singleton_subset_iff.2 h.some_mem
  · intro h
    cases' h with h h
    · refine' ⟨∅, countable_empty, closure_eq_of_le (empty_subset _) _⟩
      rw [← SetLike.coe_subset_coe, h]
      exact empty_subset _
    · obtain ⟨f, rfl⟩ := h
      exact ⟨range f, countable_range _, rfl⟩
#align first_order.language.substructure.cg_iff_empty_or_exists_nat_generating_family FirstOrder.Language.Substructure.cg_iff_empty_or_exists_nat_generating_family

theorem cg_bot : (⊥ : L.Substructure M).Cg :=
  fg_bot.Cg
#align first_order.language.substructure.cg_bot FirstOrder.Language.Substructure.cg_bot

theorem cg_closure {s : Set M} (hs : s.Countable) : Cg (closure L s) :=
  ⟨s, hs, rfl⟩
#align first_order.language.substructure.cg_closure FirstOrder.Language.Substructure.cg_closure

theorem cg_closure_singleton (x : M) : Cg (closure L ({x} : Set M)) :=
  (fg_closure_singleton x).Cg
#align first_order.language.substructure.cg_closure_singleton FirstOrder.Language.Substructure.cg_closure_singleton

theorem Cg.sup {N₁ N₂ : L.Substructure M} (hN₁ : N₁.Cg) (hN₂ : N₂.Cg) : (N₁ ⊔ N₂).Cg :=
  let ⟨t₁, ht₁⟩ := cg_def.1 hN₁
  let ⟨t₂, ht₂⟩ := cg_def.1 hN₂
  cg_def.2 ⟨t₁ ∪ t₂, ht₁.1.union ht₂.1, by rw [closure_union, ht₁.2, ht₂.2]⟩
#align first_order.language.substructure.cg.sup FirstOrder.Language.Substructure.Cg.sup

theorem Cg.map {N : Type _} [L.Structure N] (f : M →[L] N) {s : L.Substructure M} (hs : s.Cg) :
    (s.map f).Cg :=
  let ⟨t, ht⟩ := cg_def.1 hs
  cg_def.2 ⟨f '' t, ht.1.image _, by rw [closure_image, ht.2]⟩
#align first_order.language.substructure.cg.map FirstOrder.Language.Substructure.Cg.map

theorem Cg.of_map_embedding {N : Type _} [L.Structure N] (f : M ↪[L] N) {s : L.Substructure M}
    (hs : (s.map f.toHom).Cg) : s.Cg :=
  by
  rcases hs with ⟨t, h1, h2⟩
  rw [cg_def]
  refine' ⟨f ⁻¹' t, h1.preimage f.injective, _⟩
  have hf : Function.Injective f.to_hom := f.injective
  refine' map_injective_of_injective hf _
  rw [← h2, map_closure, embedding.coe_to_hom, image_preimage_eq_of_subset]
  intro x hx
  have h' := subset_closure hx
  rw [h2] at h'
  exact hom.map_le_range h'
#align first_order.language.substructure.cg.of_map_embedding FirstOrder.Language.Substructure.Cg.of_map_embedding

theorem cg_iff_countable [Countable (Σl, L.Functions l)] {s : L.Substructure M} :
    s.Cg ↔ Countable s :=
  by
  refine' ⟨_, fun h => ⟨s, h.to_set, s.closure_eq⟩⟩
  rintro ⟨s, h, rfl⟩
  exact h.substructure_closure L
#align first_order.language.substructure.cg_iff_countable FirstOrder.Language.Substructure.cg_iff_countable

end Substructure

open Substructure

namespace Structure

variable (L) (M)

/-- A structure is finitely generated if it is the closure of a finite subset. -/
class Fg : Prop where
  out : (⊤ : L.Substructure M).Fg
#align first_order.language.Structure.fg FirstOrder.Language.Structure.Fg

/-- A structure is countably generated if it is the closure of a countable subset. -/
class Cg : Prop where
  out : (⊤ : L.Substructure M).Cg
#align first_order.language.Structure.cg FirstOrder.Language.Structure.Cg

variable {L M}

theorem fg_def : Fg L M ↔ (⊤ : L.Substructure M).Fg :=
  ⟨fun h => h.1, fun h => ⟨h⟩⟩
#align first_order.language.Structure.fg_def FirstOrder.Language.Structure.fg_def

/-- An equivalent expression of `Structure.fg` in terms of `set.finite` instead of `finset`. -/
theorem fg_iff : Fg L M ↔ ∃ S : Set M, S.Finite ∧ closure L S = (⊤ : L.Substructure M) := by
  rw [fg_def, substructure.fg_def]
#align first_order.language.Structure.fg_iff FirstOrder.Language.Structure.fg_iff

theorem Fg.range {N : Type _} [L.Structure N] (h : Fg L M) (f : M →[L] N) : f.range.Fg :=
  by
  rw [hom.range_eq_map]
  exact (fg_def.1 h).map f
#align first_order.language.Structure.fg.range FirstOrder.Language.Structure.Fg.range

theorem Fg.map_of_surjective {N : Type _} [L.Structure N] (h : Fg L M) (f : M →[L] N)
    (hs : Function.Surjective f) : Fg L N :=
  by
  rw [← hom.range_eq_top] at hs
  rw [fg_def, ← hs]
  exact h.range f
#align first_order.language.Structure.fg.map_of_surjective FirstOrder.Language.Structure.Fg.map_of_surjective

theorem cg_def : Cg L M ↔ (⊤ : L.Substructure M).Cg :=
  ⟨fun h => h.1, fun h => ⟨h⟩⟩
#align first_order.language.Structure.cg_def FirstOrder.Language.Structure.cg_def

/-- An equivalent expression of `Structure.cg`. -/
theorem cg_iff : Cg L M ↔ ∃ S : Set M, S.Countable ∧ closure L S = (⊤ : L.Substructure M) := by
  rw [cg_def, substructure.cg_def]
#align first_order.language.Structure.cg_iff FirstOrder.Language.Structure.cg_iff

theorem Cg.range {N : Type _} [L.Structure N] (h : Cg L M) (f : M →[L] N) : f.range.Cg :=
  by
  rw [hom.range_eq_map]
  exact (cg_def.1 h).map f
#align first_order.language.Structure.cg.range FirstOrder.Language.Structure.Cg.range

theorem Cg.map_of_surjective {N : Type _} [L.Structure N] (h : Cg L M) (f : M →[L] N)
    (hs : Function.Surjective f) : Cg L N :=
  by
  rw [← hom.range_eq_top] at hs
  rw [cg_def, ← hs]
  exact h.range f
#align first_order.language.Structure.cg.map_of_surjective FirstOrder.Language.Structure.Cg.map_of_surjective

theorem cg_iff_countable [Countable (Σl, L.Functions l)] : Cg L M ↔ Countable M := by
  rw [cg_def, cg_iff_countable, top_equiv.to_equiv.countable_iff]
#align first_order.language.Structure.cg_iff_countable FirstOrder.Language.Structure.cg_iff_countable

theorem Fg.cg (h : Fg L M) : Cg L M :=
  cg_def.2 (fg_def.1 h).Cg
#align first_order.language.Structure.fg.cg FirstOrder.Language.Structure.Fg.cg

instance (priority := 100) cg_of_fg [h : Fg L M] : Cg L M :=
  h.Cg
#align first_order.language.Structure.cg_of_fg FirstOrder.Language.Structure.cg_of_fg

end Structure

theorem Equiv.fg_iff {N : Type _} [L.Structure N] (f : M ≃[L] N) :
    Structure.Fg L M ↔ Structure.Fg L N :=
  ⟨fun h => h.mapOfSurjective f.toHom f.toEquiv.Surjective, fun h =>
    h.mapOfSurjective f.symm.toHom f.toEquiv.symm.Surjective⟩
#align first_order.language.equiv.fg_iff FirstOrder.Language.Equiv.fg_iff

theorem Substructure.fg_iff_structure_fg (S : L.Substructure M) : S.Fg ↔ Structure.Fg L S :=
  by
  rw [Structure.fg_def]
  refine' ⟨fun h => fg.of_map_embedding S.subtype _, fun h => _⟩
  · rw [← hom.range_eq_map, range_subtype]
    exact h
  · have h := h.map S.subtype.to_hom
    rw [← hom.range_eq_map, range_subtype] at h
    exact h
#align first_order.language.substructure.fg_iff_Structure_fg FirstOrder.Language.Substructure.fg_iff_structure_fg

theorem Equiv.cg_iff {N : Type _} [L.Structure N] (f : M ≃[L] N) :
    Structure.Cg L M ↔ Structure.Cg L N :=
  ⟨fun h => h.mapOfSurjective f.toHom f.toEquiv.Surjective, fun h =>
    h.mapOfSurjective f.symm.toHom f.toEquiv.symm.Surjective⟩
#align first_order.language.equiv.cg_iff FirstOrder.Language.Equiv.cg_iff

theorem Substructure.cg_iff_structure_cg (S : L.Substructure M) : S.Cg ↔ Structure.Cg L S :=
  by
  rw [Structure.cg_def]
  refine' ⟨fun h => cg.of_map_embedding S.subtype _, fun h => _⟩
  · rw [← hom.range_eq_map, range_subtype]
    exact h
  · have h := h.map S.subtype.to_hom
    rw [← hom.range_eq_map, range_subtype] at h
    exact h
#align first_order.language.substructure.cg_iff_Structure_cg FirstOrder.Language.Substructure.cg_iff_structure_cg

end Language

end FirstOrder


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

Disjointed sets
-/
import data.set.lattice
import tactic.wlog
open set classical
open_locale classical

universes u v w x
variables {α : Type u} {β : Type v} {γ : Type w} {ι : Sort x}
  {s t u : set α}

/-- A relation `p` holds pairwise if `p i j` for all `i ≠ j`. -/
def pairwise {α : Type*} (p : α → α → Prop) := ∀i j, i ≠ j → p i j

theorem set.pairwise_on_univ {r : α → α → Prop} :
  (univ : set α).pairwise_on r ↔ pairwise r :=
by simp only [pairwise_on, pairwise, mem_univ, forall_const]

theorem set.pairwise_on.on_injective {s : set α} {r : α → α → Prop} (hs : pairwise_on s r)
  {f : β → α} (hf : function.injective f) (hfs : ∀ x, f x ∈ s) :
  pairwise (r on f) :=
λ i j hij, hs _ (hfs i) _ (hfs j) (hf.ne hij)

theorem pairwise.mono {p q : α → α → Prop} (h : ∀ ⦃i j⦄, p i j → q i j) (hp : pairwise p) :
  pairwise q :=
λ i j hij, h (hp i j hij)

theorem pairwise_on_bool {r} (hr : symmetric r) {a b : α} :
  pairwise (r on (λ c, cond c a b)) ↔ r a b :=
by simpa [pairwise, function.on_fun] using @hr a b

theorem pairwise_disjoint_on_bool [semilattice_inf_bot α] {a b : α} :
  pairwise (disjoint on (λ c, cond c a b)) ↔ disjoint a b :=
pairwise_on_bool disjoint.symm

theorem pairwise.pairwise_on {p : α → α → Prop} (h : pairwise p) (s : set α) : s.pairwise_on p :=
λ x hx y hy, h x y

theorem pairwise_disjoint_fiber (f : α → β) : pairwise (disjoint on (λ y : β, f ⁻¹' {y})) :=
set.pairwise_on_univ.1 $ pairwise_on_disjoint_fiber f univ

namespace set

/-- If `f : ℕ → set α` is a sequence of sets, then `disjointed f` is
  the sequence formed with each set subtracted from the later ones
  in the sequence, to form a disjoint sequence. -/
def disjointed (f : ℕ → set α) (n : ℕ) : set α := f n ∩ (⋂i<n, (f i)ᶜ)

lemma disjoint_disjointed {f : ℕ → set α} : pairwise (disjoint on disjointed f) :=
λ i j h, begin
  wlog h' : i ≤ j; [skip, {revert a, exact (this h.symm).symm}],
  rintro a ⟨⟨h₁, _⟩, h₂, h₃⟩, simp at h₃,
  exact h₃ _ (lt_of_le_of_ne h' h) h₁
end

lemma disjoint_disjointed' {f : ℕ → set α} :
  ∀ i j, i ≠ j → (disjointed f i) ∩ (disjointed f j) = ∅ :=
λ i j hij, disjoint_iff.1 $ disjoint_disjointed i j hij

lemma disjointed_subset {f : ℕ → set α} {n : ℕ} : disjointed f n ⊆ f n := inter_subset_left _ _

lemma Union_lt_succ {f : ℕ → set α} {n} : (⋃i < nat.succ n, f i) = f n ∪ (⋃i < n, f i) :=
ext $ λ a, by simp [nat.lt_succ_iff_lt_or_eq, or_and_distrib_right, exists_or_distrib, or_comm]

lemma Inter_lt_succ {f : ℕ → set α} {n} : (⋂i < nat.succ n, f i) = f n ∩ (⋂i < n, f i) :=
ext $ λ a, by simp [nat.lt_succ_iff_lt_or_eq, or_imp_distrib, forall_and_distrib, and_comm]

lemma subset_Union_disjointed {f : ℕ → set α} {n} : f n ⊆ ⋃ i < n.succ, disjointed f i :=
λ x hx,
  have ∃ k, x ∈ f k, from ⟨n, hx⟩,
  have hn : ∀ (i : ℕ), i < nat.find this → x ∉ f i,
    from assume i, nat.find_min this,
  have hlt : nat.find this < n.succ,
    from (nat.find_min' this hx).trans_lt n.lt_succ_self,
  mem_bUnion hlt ⟨nat.find_spec this, mem_bInter hn⟩

lemma Union_disjointed {f : ℕ → set α} : (⋃n, disjointed f n) = (⋃n, f n) :=
subset.antisymm (Union_subset_Union $ assume i, inter_subset_left _ _)
  (Union_subset $ λ n, subset.trans subset_Union_disjointed (bUnion_subset_Union _ _))

lemma disjointed_induct {f : ℕ → set α} {n : ℕ} {p : set α → Prop}
  (h₁ : p (f n)) (h₂ : ∀t i, p t → p (t \ f i)) :
  p (disjointed f n) :=
begin
  rw disjointed,
  generalize_hyp : f n = t at h₁ ⊢,
  induction n,
  case nat.zero { simp [nat.not_lt_zero, h₁] },
  case nat.succ : n ih {
    rw [Inter_lt_succ, inter_comm ((f n)ᶜ), ← inter_assoc],
    exact h₂ _ n ih }
end

lemma disjointed_of_mono {f : ℕ → set α} {n : ℕ} (hf : monotone f) :
  disjointed f (n + 1) = f (n + 1) \ f n :=
have (⋂i (h : i < n + 1), (f i)ᶜ) = (f n)ᶜ,
  from le_antisymm
    (infi_le_of_le n $ infi_le_of_le (nat.lt_succ_self _) $ subset.refl _)
    (le_infi $ assume i, le_infi $ assume hi, compl_le_compl $ hf $ nat.le_of_succ_le_succ hi),
by simp [disjointed, this, diff_eq]

lemma Union_disjointed_of_mono {f : ℕ → set α} (hf : monotone f) (n : ℕ) :
  (⋃i<n.succ, disjointed f i) = f n :=
subset.antisymm (bUnion_subset $ λ k hk, subset.trans disjointed_subset $ hf $ nat.lt_succ_iff.1 hk)
  subset_Union_disjointed

end set


/-
Copyright (c) 2018 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl

! This file was ported from Lean 3 source module data.multiset.pi
! leanprover-community/mathlib commit 4c586d291f189eecb9d00581aeb3dd998ac34442
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Multiset.Nodup

/-!
# The cartesian product of multisets

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

namespace Multiset

section Pi

variable {α : Type _}

open Function

#print Multiset.Pi.empty /-
/-- Given `δ : α → Type*`, `pi.empty δ` is the trivial dependent function out of the empty
multiset. -/
def Pi.empty (δ : α → Sort _) : ∀ a ∈ (0 : Multiset α), δ a :=
  fun.
#align multiset.pi.empty Multiset.Pi.empty
-/

variable [DecidableEq α] {β : α → Type _} {δ : α → Sort _}

/- warning: multiset.pi.cons -> Multiset.Pi.cons is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} (m : Multiset.{u1} α) (a : α), (δ a) -> (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (δ a)) -> (forall (a' : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' (Multiset.cons.{u1} α a m)) -> (δ a'))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : DecidableEq.{succ u2} α] {δ : α -> Sort.{u1}} (m : Multiset.{u2} α) (a : α), (δ a) -> (forall (a : α), (Membership.mem.{u2, u2} α (Multiset.{u2} α) (Multiset.instMembershipMultiset.{u2} α) a m) -> (δ a)) -> (forall (a' : α), (Membership.mem.{u2, u2} α (Multiset.{u2} α) (Multiset.instMembershipMultiset.{u2} α) a' (Multiset.cons.{u2} α a m)) -> (δ a'))
Case conversion may be inaccurate. Consider using '#align multiset.pi.cons Multiset.Pi.consₓ'. -/
/-- Given `δ : α → Type*`, a multiset `m` and a term `a`, as well as a term `b : δ a` and a
function `f` such that `f a' : δ a'` for all `a'` in `m`, `pi.cons m a b f` is a function `g` such
that `g a'' : δ a''` for all `a''` in `a ::ₘ m`. -/
def Pi.cons (m : Multiset α) (a : α) (b : δ a) (f : ∀ a ∈ m, δ a) : ∀ a' ∈ a ::ₘ m, δ a' :=
  fun a' ha' => if h : a' = a then Eq.ndrec b h.symm else f a' <| (mem_cons.1 ha').resolve_left h
#align multiset.pi.cons Multiset.Pi.cons

/- warning: multiset.pi.cons_same -> Multiset.Pi.cons_same is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {m : Multiset.{u1} α} {a : α} {b : δ a} {f : forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (δ a)} (h : Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a (Multiset.cons.{u1} α a m)), Eq.{u2} (δ a) (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun {a : α} => δ a) m a b f a h) b
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {m : Multiset.{u1} α} {a : α} {b : δ a} {f : forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (δ a)} (h : Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a (Multiset.cons.{u1} α a m)), Eq.{u2} (δ a) (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) δ m a b f a h) b
Case conversion may be inaccurate. Consider using '#align multiset.pi.cons_same Multiset.Pi.cons_sameₓ'. -/
theorem Pi.cons_same {m : Multiset α} {a : α} {b : δ a} {f : ∀ a ∈ m, δ a} (h : a ∈ a ::ₘ m) :
    Pi.cons m a b f a h = b :=
  dif_pos rfl
#align multiset.pi.cons_same Multiset.Pi.cons_same

/- warning: multiset.pi.cons_ne -> Multiset.Pi.cons_ne is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {m : Multiset.{u1} α} {a : α} {a' : α} {b : δ a} {f : forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (δ a)} (h' : Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' (Multiset.cons.{u1} α a m)) (h : Ne.{succ u1} α a' a), Eq.{u2} (δ a') (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun {a : α} => δ a) m a b f a' h') (f a' (Or.resolve_left (Eq.{succ u1} α a' a) (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' m) (Iff.mp (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' (Multiset.cons.{u1} α a m)) (Or (Eq.{succ u1} α a' a) (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' m)) (Multiset.mem_cons.{u1} α a' a m) h') h))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {m : Multiset.{u1} α} {a : α} {a' : α} {b : δ a} {f : forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (δ a)} (h' : Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' (Multiset.cons.{u1} α a m)) (h : Ne.{succ u1} α a' a), Eq.{u2} (δ a') (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) δ m a b f a' h') (f a' (Or.resolve_left (Eq.{succ u1} α a' a) (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' m) (Iff.mp (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' (Multiset.cons.{u1} α a m)) (Or (Eq.{succ u1} α a' a) (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' m)) (Multiset.mem_cons.{u1} α a' a m) h') h))
Case conversion may be inaccurate. Consider using '#align multiset.pi.cons_ne Multiset.Pi.cons_neₓ'. -/
theorem Pi.cons_ne {m : Multiset α} {a a' : α} {b : δ a} {f : ∀ a ∈ m, δ a} (h' : a' ∈ a ::ₘ m)
    (h : a' ≠ a) : Pi.cons m a b f a' h' = f a' ((mem_cons.1 h').resolve_left h) :=
  dif_neg h
#align multiset.pi.cons_ne Multiset.Pi.cons_ne

/- warning: multiset.pi.cons_swap -> Multiset.Pi.cons_swap is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {a : α} {a' : α} {b : δ a} {b' : δ a'} {m : Multiset.{u1} α} {f : forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (δ a)}, (Ne.{succ u1} α a a') -> (HEq.{imax (succ u1) u2} (forall (a'_1 : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a'_1 (Multiset.cons.{u1} α a (Multiset.cons.{u1} α a' m))) -> (δ a'_1)) (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun {a : α} => δ a) (Multiset.cons.{u1} α a' m) a b (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => δ a) m a' b' f)) (forall (a'_1 : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a'_1 (Multiset.cons.{u1} α a' (Multiset.cons.{u1} α a m))) -> (δ a'_1)) (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun {a' : α} => δ a') (Multiset.cons.{u1} α a m) a' b' (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => δ a) m a b f)))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {a : α} {a' : α} {b : δ a} {b' : δ a'} {m : Multiset.{u1} α} {f : forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (δ a)}, (Ne.{succ u1} α a a') -> (HEq.{imax (succ u1) u2} (forall (a'_1 : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a'_1 (Multiset.cons.{u1} α a (Multiset.cons.{u1} α a' m))) -> (δ a'_1)) (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) δ (Multiset.cons.{u1} α a' m) a b (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => δ a) m a' b' f)) (forall (a'_1 : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a'_1 (Multiset.cons.{u1} α a' (Multiset.cons.{u1} α a m))) -> (δ a'_1)) (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) δ (Multiset.cons.{u1} α a m) a' b' (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => δ a) m a b f)))
Case conversion may be inaccurate. Consider using '#align multiset.pi.cons_swap Multiset.Pi.cons_swapₓ'. -/
theorem Pi.cons_swap {a a' : α} {b : δ a} {b' : δ a'} {m : Multiset α} {f : ∀ a ∈ m, δ a}
    (h : a ≠ a') :
    HEq (Pi.cons (a' ::ₘ m) a b (Pi.cons m a' b' f)) (Pi.cons (a ::ₘ m) a' b' (Pi.cons m a b f)) :=
  by
  apply hfunext rfl
  rintro a'' _ rfl
  refine' hfunext (by rw [cons_swap]) fun ha₁ ha₂ _ => _
  rcases ne_or_eq a'' a with (h₁ | rfl)
  rcases eq_or_ne a'' a' with (rfl | h₂)
  all_goals simp [*, pi.cons_same, pi.cons_ne]
#align multiset.pi.cons_swap Multiset.Pi.cons_swap

/- warning: multiset.pi -> Multiset.pi is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (m : Multiset.{u1} α), (forall (a : α), Multiset.{u2} (β a)) -> (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a)))
but is expected to have type
  forall {α : Type.{u2}} [_inst_1 : DecidableEq.{succ u2} α] {β : α -> Type.{u1}} (m : Multiset.{u2} α), (forall (a : α), Multiset.{u1} (β a)) -> (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u2, u2} α (Multiset.{u2} α) (Multiset.instMembershipMultiset.{u2} α) a m) -> (β a)))
Case conversion may be inaccurate. Consider using '#align multiset.pi Multiset.piₓ'. -/
/-- `pi m t` constructs the Cartesian product over `t` indexed by `m`. -/
def pi (m : Multiset α) (t : ∀ a, Multiset (β a)) : Multiset (∀ a ∈ m, β a) :=
  m.recOn {Pi.empty β}
    (fun a m (p : Multiset (∀ a ∈ m, β a)) => (t a).bind fun b => p.map <| Pi.cons m a b)
    (by
      intro a a' m n
      by_cases eq : a = a'
      · subst Eq
      · simp [map_bind, bind_bind (t a') (t a)]
        apply bind_hcongr
        · rw [cons_swap a a']
        intro b hb
        apply bind_hcongr
        · rw [cons_swap a a']
        intro b' hb'
        apply map_hcongr
        · rw [cons_swap a a']
        intro f hf
        exact pi.cons_swap Eq)
#align multiset.pi Multiset.pi

/- warning: multiset.pi_zero -> Multiset.pi_zero is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (t : forall (a : α), Multiset.{u2} (β a)), Eq.{succ (max u1 u2)} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (OfNat.mk.{u1} (Multiset.{u1} α) 0 (Zero.zero.{u1} (Multiset.{u1} α) (Multiset.hasZero.{u1} α))))) -> (β a))) (Multiset.pi.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (OfNat.mk.{u1} (Multiset.{u1} α) 0 (Zero.zero.{u1} (Multiset.{u1} α) (Multiset.hasZero.{u1} α)))) t) (Singleton.singleton.{max u1 u2, max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (OfNat.mk.{u1} (Multiset.{u1} α) 0 (Zero.zero.{u1} (Multiset.{u1} α) (Multiset.hasZero.{u1} α))))) -> (β a)) (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (OfNat.mk.{u1} (Multiset.{u1} α) 0 (Zero.zero.{u1} (Multiset.{u1} α) (Multiset.hasZero.{u1} α))))) -> (β a))) (Multiset.hasSingleton.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (OfNat.mk.{u1} (Multiset.{u1} α) 0 (Zero.zero.{u1} (Multiset.{u1} α) (Multiset.hasZero.{u1} α))))) -> (β a))) (Multiset.Pi.empty.{u1, succ u2} α β))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (t : forall (a : α), Multiset.{u2} (β a)), Eq.{max (succ u2) (succ u1)} (Multiset.{max u2 u1} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (Zero.toOfNat0.{u1} (Multiset.{u1} α) (Multiset.instZeroMultiset.{u1} α)))) -> (β a))) (Multiset.pi.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (Zero.toOfNat0.{u1} (Multiset.{u1} α) (Multiset.instZeroMultiset.{u1} α))) t) (Singleton.singleton.{max u2 u1, max u2 u1} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (Zero.toOfNat0.{u1} (Multiset.{u1} α) (Multiset.instZeroMultiset.{u1} α)))) -> (β a)) (Multiset.{max u2 u1} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (Zero.toOfNat0.{u1} (Multiset.{u1} α) (Multiset.instZeroMultiset.{u1} α)))) -> (β a))) (Multiset.instSingletonMultiset.{max u2 u1} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a (OfNat.ofNat.{u1} (Multiset.{u1} α) 0 (Zero.toOfNat0.{u1} (Multiset.{u1} α) (Multiset.instZeroMultiset.{u1} α)))) -> (β a))) (Multiset.Pi.empty.{u1, succ u2} α β))
Case conversion may be inaccurate. Consider using '#align multiset.pi_zero Multiset.pi_zeroₓ'. -/
@[simp]
theorem pi_zero (t : ∀ a, Multiset (β a)) : pi 0 t = {Pi.empty β} :=
  rfl
#align multiset.pi_zero Multiset.pi_zero

/- warning: multiset.pi_cons -> Multiset.pi_cons is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (m : Multiset.{u1} α) (t : forall (a : α), Multiset.{u2} (β a)) (a : α), Eq.{succ (max u1 u2)} (Multiset.{max u1 u2} (forall (a_1 : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a_1 (Multiset.cons.{u1} α a m)) -> (β a_1))) (Multiset.pi.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) (Multiset.cons.{u1} α a m) t) (Multiset.bind.{u2, max u1 u2} (β a) (forall (a_1 : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a_1 (Multiset.cons.{u1} α a m)) -> (β a_1)) (t a) (fun (b : β a) => Multiset.map.{max u1 u2, max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a)) (forall (a_1 : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a_1 (Multiset.cons.{u1} α a m)) -> (β a_1)) (Multiset.Pi.cons.{u1, succ u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m a b) (Multiset.pi.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m t)))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (m : Multiset.{u1} α) (t : forall (a : α), Multiset.{u2} (β a)) (a : α), Eq.{max (succ u2) (succ u1)} (Multiset.{max u2 u1} (forall (a_1 : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a_1 (Multiset.cons.{u1} α a m)) -> (β a_1))) (Multiset.pi.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) (Multiset.cons.{u1} α a m) t) (Multiset.bind.{u2, max u2 u1} (β a) (forall (a_1 : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a_1 (Multiset.cons.{u1} α a m)) -> (β a_1)) (t a) (fun (b : β a) => Multiset.map.{max u2 u1, max u2 u1} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)) (forall (a_1 : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a_1 (Multiset.cons.{u1} α a m)) -> (β a_1)) (Multiset.Pi.cons.{succ u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) β m a b) (Multiset.pi.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m t)))
Case conversion may be inaccurate. Consider using '#align multiset.pi_cons Multiset.pi_consₓ'. -/
@[simp]
theorem pi_cons (m : Multiset α) (t : ∀ a, Multiset (β a)) (a : α) :
    pi (a ::ₘ m) t = (t a).bind fun b => (pi m t).map <| Pi.cons m a b :=
  recOn_cons a m
#align multiset.pi_cons Multiset.pi_cons

/- warning: multiset.pi_cons_injective -> Multiset.pi_cons_injective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {a : α} {b : δ a} {s : Multiset.{u1} α}, (Not (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a s)) -> (Function.Injective.{imax (succ u1) u2, imax (succ u1) u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a s) -> (δ a)) (forall (a' : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' (Multiset.cons.{u1} α a s)) -> (δ a')) (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun {a : α} => δ a) s a b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {a : α} {b : δ a} {s : Multiset.{u1} α}, (Not (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a s)) -> (Function.Injective.{imax (succ u1) u2, imax (succ u1) u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a s) -> (δ a)) (forall (a' : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' (Multiset.cons.{u1} α a s)) -> (δ a')) (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) δ s a b))
Case conversion may be inaccurate. Consider using '#align multiset.pi_cons_injective Multiset.pi_cons_injectiveₓ'. -/
theorem pi_cons_injective {a : α} {b : δ a} {s : Multiset α} (hs : a ∉ s) :
    Function.Injective (Pi.cons s a b) := fun f₁ f₂ eq =>
  funext fun a' =>
    funext fun h' =>
      have ne : a ≠ a' := fun h => hs <| h.symm ▸ h'
      have : a' ∈ a ::ₘ s := mem_cons_of_mem h'
      calc
        f₁ a' h' = Pi.cons s a b f₁ a' this := by rw [pi.cons_ne this Ne.symm]
        _ = Pi.cons s a b f₂ a' this := by rw [Eq]
        _ = f₂ a' h' := by rw [pi.cons_ne this Ne.symm]

#align multiset.pi_cons_injective Multiset.pi_cons_injective

/- warning: multiset.card_pi -> Multiset.card_pi is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (m : Multiset.{u1} α) (t : forall (a : α), Multiset.{u2} (β a)), Eq.{1} Nat (coeFn.{succ (max u1 u2), succ (max u1 u2)} (AddMonoidHom.{max u1 u2, 0} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (Multiset.orderedCancelAddCommMonoid.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (fun (_x : AddMonoidHom.{max u1 u2, 0} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (Multiset.orderedCancelAddCommMonoid.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) => (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) -> Nat) (AddMonoidHom.hasCoeToFun.{max u1 u2, 0} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (Multiset.orderedCancelAddCommMonoid.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.card.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (Multiset.pi.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m t)) (Multiset.prod.{0} Nat Nat.commMonoid (Multiset.map.{u1, 0} α Nat (fun (a : α) => coeFn.{succ u2, succ u2} (AddMonoidHom.{u2, 0} (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.orderedCancelAddCommMonoid.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (fun (_x : AddMonoidHom.{u2, 0} (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.orderedCancelAddCommMonoid.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) => (Multiset.{u2} (β a)) -> Nat) (AddMonoidHom.hasCoeToFun.{u2, 0} (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.orderedCancelAddCommMonoid.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.card.{u2} (β a)) (t a)) m))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (m : Multiset.{u1} α) (t : forall (a : α), Multiset.{u2} (β a)), Eq.{1} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) => Nat) (Multiset.pi.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m t)) (FunLike.coe.{succ (max u1 u2), succ (max u1 u2), 1} (AddMonoidHom.{max u1 u2, 0} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.instOrderedCancelAddCommMonoidMultiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (fun (_x : Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) => Nat) _x) (AddHomClass.toFunLike.{max u1 u2, max u1 u2, 0} (AddMonoidHom.{max u1 u2, 0} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.instOrderedCancelAddCommMonoidMultiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) Nat (AddZeroClass.toAdd.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.instOrderedCancelAddCommMonoidMultiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))))))))) (AddZeroClass.toAdd.{0} Nat (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (AddMonoidHomClass.toAddHomClass.{max u1 u2, max u1 u2, 0} (AddMonoidHom.{max u1 u2, 0} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.instOrderedCancelAddCommMonoidMultiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.instOrderedCancelAddCommMonoidMultiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid) (AddMonoidHom.addMonoidHomClass.{max u1 u2, 0} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) Nat (AddMonoid.toAddZeroClass.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddRightCancelMonoid.toAddMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelMonoid.toAddRightCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (AddCancelCommMonoid.toAddCancelMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{max u1 u2} (Multiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.instOrderedCancelAddCommMonoidMultiset.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)))) (Multiset.card.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.pi.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m t)) (Multiset.prod.{0} Nat Nat.commMonoid (Multiset.map.{u1, 0} α Nat (fun (a : α) => FunLike.coe.{succ u2, succ u2, 1} (AddMonoidHom.{u2, 0} (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{u2} (β a)) (fun (_x : Multiset.{u2} (β a)) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.403 : Multiset.{u2} (β a)) => Nat) _x) (AddHomClass.toFunLike.{u2, u2, 0} (AddMonoidHom.{u2, 0} (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{u2} (β a)) Nat (AddZeroClass.toAdd.{u2} (Multiset.{u2} (β a)) (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{u2} (β a)))))))) (AddZeroClass.toAdd.{0} Nat (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (AddMonoidHomClass.toAddHomClass.{u2, u2, 0} (AddMonoidHom.{u2, 0} (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)) (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid) (AddMonoidHom.addMonoidHomClass.{u2, 0} (Multiset.{u2} (β a)) Nat (AddMonoid.toAddZeroClass.{u2} (Multiset.{u2} (β a)) (AddRightCancelMonoid.toAddMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelMonoid.toAddRightCancelMonoid.{u2} (Multiset.{u2} (β a)) (AddCancelCommMonoid.toAddCancelMonoid.{u2} (Multiset.{u2} (β a)) (OrderedCancelAddCommMonoid.toCancelAddCommMonoid.{u2} (Multiset.{u2} (β a)) (Multiset.instOrderedCancelAddCommMonoidMultiset.{u2} (β a))))))) (AddMonoid.toAddZeroClass.{0} Nat Nat.addMonoid)))) (Multiset.card.{u2} (β a)) (t a)) m))
Case conversion may be inaccurate. Consider using '#align multiset.card_pi Multiset.card_piₓ'. -/
theorem card_pi (m : Multiset α) (t : ∀ a, Multiset (β a)) :
    card (pi m t) = prod (m.map fun a => card (t a)) :=
  Multiset.induction_on m (by simp) (by simp (config := { contextual := true }) [mul_comm])
#align multiset.card_pi Multiset.card_pi

/- warning: multiset.nodup.pi -> Multiset.Nodup.pi is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} {s : Multiset.{u1} α} {t : forall (a : α), Multiset.{u2} (β a)}, (Multiset.Nodup.{u1} α s) -> (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a s) -> (Multiset.Nodup.{u2} (β a) (t a))) -> (Multiset.Nodup.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a s) -> (β a)) (Multiset.pi.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) s t))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} {s : Multiset.{u1} α} {t : forall (a : α), Multiset.{u2} (β a)}, (Multiset.Nodup.{u1} α s) -> (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a s) -> (Multiset.Nodup.{u2} (β a) (t a))) -> (Multiset.Nodup.{max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a s) -> (β a)) (Multiset.pi.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) s t))
Case conversion may be inaccurate. Consider using '#align multiset.nodup.pi Multiset.Nodup.piₓ'. -/
protected theorem Nodup.pi {s : Multiset α} {t : ∀ a, Multiset (β a)} :
    Nodup s → (∀ a ∈ s, Nodup (t a)) → Nodup (pi s t) :=
  Multiset.induction_on s (fun _ _ => nodup_singleton _)
    (by
      intro a s ih hs ht
      have has : a ∉ s := by simp at hs <;> exact hs.1
      have hs : nodup s := by simp at hs <;> exact hs.2
      simp
      refine'
        ⟨fun b hb => (ih hs fun a' h' => ht a' <| mem_cons_of_mem h').map (pi_cons_injective has),
          _⟩
      refine' (ht a <| mem_cons_self _ _).Pairwise _
      exact fun b₁ hb₁ b₂ hb₂ neb =>
        disjoint_map_map.2 fun f hf g hg eq =>
          have : pi.cons s a b₁ f a (mem_cons_self _ _) = pi.cons s a b₂ g a (mem_cons_self _ _) :=
            by rw [Eq]
          neb <| show b₁ = b₂ by rwa [pi.cons_same, pi.cons_same] at this)
#align multiset.nodup.pi Multiset.Nodup.pi

/- warning: multiset.pi.cons_ext -> Multiset.pi.cons_ext is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {m : Multiset.{u1} α} {a : α} (f : forall (a' : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' (Multiset.cons.{u1} α a m)) -> (δ a')), Eq.{imax (succ u1) u2} (forall (a' : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' (Multiset.cons.{u1} α a m)) -> (δ a')) (Multiset.Pi.cons.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun {a : α} => δ a) m a (f a (Multiset.mem_cons_self.{u1} α a m)) (fun (a' : α) (ha' : Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a' m) => f a' (Multiset.mem_cons_of_mem.{u1} α a' a m ha'))) f
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {δ : α -> Sort.{u2}} {m : Multiset.{u1} α} {a : α} (f : forall (a' : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' (Multiset.cons.{u1} α a m)) -> (δ a')), Eq.{imax (succ u1) u2} (forall (a' : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' (Multiset.cons.{u1} α a m)) -> (δ a')) (Multiset.Pi.cons.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) δ m a (f a (Multiset.mem_cons_self.{u1} α a m)) (fun (a' : α) (ha' : Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a' m) => f a' (Multiset.mem_cons_of_mem.{u1} α a' a m ha'))) f
Case conversion may be inaccurate. Consider using '#align multiset.pi.cons_ext Multiset.pi.cons_extₓ'. -/
@[simp]
theorem pi.cons_ext {m : Multiset α} {a : α} (f : ∀ a' ∈ a ::ₘ m, δ a') :
    (Pi.cons m a (f _ (mem_cons_self _ _)) fun a' ha' => f a' (mem_cons_of_mem ha')) = f :=
  by
  ext (a' h')
  by_cases a' = a
  · subst h
    rw [pi.cons_same]
  · rw [pi.cons_ne _ h]
#align multiset.pi.cons_ext Multiset.pi.cons_ext

/- warning: multiset.mem_pi -> Multiset.mem_pi is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (m : Multiset.{u1} α) (t : forall (a : α), Multiset.{u2} (β a)) (f : forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a)), Iff (Membership.Mem.{max u1 u2, max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a)) (Multiset.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) (Multiset.hasMem.{max u1 u2} (forall (a : α), (Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m) -> (β a))) f (Multiset.pi.{u1, u2} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m t)) (forall (a : α) (h : Membership.Mem.{u1, u1} α (Multiset.{u1} α) (Multiset.hasMem.{u1} α) a m), Membership.Mem.{u2, u2} (β a) (Multiset.{u2} (β a)) (Multiset.hasMem.{u2} (β a)) (f a h) (t a))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : DecidableEq.{succ u1} α] {β : α -> Type.{u2}} (m : Multiset.{u1} α) (t : forall (a : α), Multiset.{u2} (β a)) (f : forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)), Iff (Membership.mem.{max u2 u1, max u1 u2} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a)) (Multiset.{max u2 u1} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) (Multiset.instMembershipMultiset.{max u2 u1} (forall (a : α), (Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m) -> (β a))) f (Multiset.pi.{u2, u1} α (fun (a : α) (b : α) => _inst_1 a b) (fun (a : α) => β a) m t)) (forall (a : α) (h : Membership.mem.{u1, u1} α (Multiset.{u1} α) (Multiset.instMembershipMultiset.{u1} α) a m), Membership.mem.{u2, u2} (β a) (Multiset.{u2} (β a)) (Multiset.instMembershipMultiset.{u2} (β a)) (f a h) (t a))
Case conversion may be inaccurate. Consider using '#align multiset.mem_pi Multiset.mem_piₓ'. -/
theorem mem_pi (m : Multiset α) (t : ∀ a, Multiset (β a)) :
    ∀ f : ∀ a ∈ m, β a, f ∈ pi m t ↔ ∀ (a) (h : a ∈ m), f a h ∈ t a :=
  by
  intro f
  induction' m using Multiset.induction_on with a m ih
  · simpa using show f = pi.empty β by funext a ha <;> exact ha.elim
  simp_rw [pi_cons, mem_bind, mem_map, ih]
  constructor
  · rintro ⟨b, hb, f', hf', rfl⟩ a' ha'
    by_cases a' = a
    · subst h
      rwa [pi.cons_same]
    · rw [pi.cons_ne _ h]
      apply hf'
  · intro hf
    refine' ⟨_, hf a (mem_cons_self _ _), _, fun a ha => hf a (mem_cons_of_mem ha), _⟩
    rw [pi.cons_ext]
#align multiset.mem_pi Multiset.mem_pi

end Pi

end Multiset


/-
Copyright (c) 2021 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel

! This file was ported from Lean 3 source module algebra.category.Module.subobject
! leanprover-community/mathlib commit 6d584f1709bedbed9175bd9350df46599bdd7213
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Category.Module.EpiMono
import Mathbin.Algebra.Category.Module.Kernels
import Mathbin.CategoryTheory.Subobject.WellPowered
import Mathbin.CategoryTheory.Subobject.Limits

/-!
# Subobjects in the category of `R`-modules

We construct an explicit order isomorphism between the categorical subobjects of an `R`-module `M`
and its submodules. This immediately implies that the category of `R`-modules is well-powered.

-/

open CategoryTheory

open CategoryTheory.Subobject

open CategoryTheory.Limits

open ModuleCat

universe v u

namespace ModuleCat

variable {R : Type u} [Ring R] (M : ModuleCat.{v} R)

/-- The categorical subobjects of a module `M` are in one-to-one correspondence with its
    submodules.-/
noncomputable def subobjectModule : Subobject M ≃o Submodule R M :=
  OrderIso.symm
    { invFun := fun S => S.arrow.range
      toFun := fun N => Subobject.mk (↾N.Subtype)
      right_inv := fun S =>
        Eq.symm
          (by
            fapply eq_mk_of_comm
            · apply LinearEquiv.toModuleIso'Left
              apply LinearEquiv.ofBijective (LinearMap.codRestrict S.arrow.range S.arrow _)
              constructor
              ·
                simpa only [← LinearMap.ker_eq_bot, LinearMap.ker_codRestrict] using
                  ker_eq_bot_of_mono _
              ·
                rw [← LinearMap.range_eq_top, LinearMap.range_codRestrict,
                  Submodule.comap_subtype_self]
              · exact LinearMap.mem_range_self _
            · apply LinearMap.ext
              intro x
              rfl)
      left_inv := fun N =>
        by
        convert congr_arg LinearMap.range (underlying_iso_arrow (↾N.subtype)) using 1
        · have :
            (underlying_iso (↾N.subtype)).inv = (underlying_iso (↾N.subtype)).symm.toLinearEquiv :=
            by
            apply LinearMap.ext
            intro x
            rfl
          rw [this, comp_def, LinearEquiv.range_comp]
        · exact (Submodule.range_subtype _).symm
      map_rel_iff' := fun S T =>
        by
        refine'
          ⟨fun h => _, fun h =>
            mk_le_mk_of_comm (↟(Submodule.ofLe h))
              (by
                ext
                rfl)⟩
        convert LinearMap.range_comp_le_range (of_mk_le_mk _ _ h) (↾T.subtype)
        · simpa only [← comp_def, of_mk_le_mk_comp] using (Submodule.range_subtype _).symm
        · exact (Submodule.range_subtype _).symm }
#align Module.subobject_Module ModuleCat.subobjectModule

instance wellPowered_moduleCat : WellPowered (ModuleCat.{v} R) :=
  ⟨fun M => ⟨⟨_, ⟨(subobjectModule M).toEquiv⟩⟩⟩⟩
#align Module.well_powered_Module ModuleCat.wellPowered_moduleCat

attribute [local instance] has_kernels_Module

/-- Bundle an element `m : M` such that `f m = 0` as a term of `kernel_subobject f`. -/
noncomputable def toKernelSubobject {M N : ModuleCat R} {f : M ⟶ N} :
    LinearMap.ker f →ₗ[R] kernelSubobject f :=
  (kernelSubobjectIso f ≪≫ ModuleCat.kernelIsoKer f).inv
#align Module.to_kernel_subobject ModuleCat.toKernelSubobject

@[simp]
theorem toKernelSubobject_arrow {M N : ModuleCat R} {f : M ⟶ N} (x : LinearMap.ker f) :
    (kernelSubobject f).arrow (toKernelSubobject x) = x.1 := by simp [to_kernel_subobject]
#align Module.to_kernel_subobject_arrow ModuleCat.toKernelSubobject_arrow

/-- An extensionality lemma showing that two elements of a cokernel by an image
are equal if they differ by an element of the image.

The application is for homology:
two elements in homology are equal if they differ by a boundary.
-/
@[ext]
theorem cokernel_π_imageSubobject_ext {L M N : ModuleCat.{v} R} (f : L ⟶ M) [HasImage f]
    (g : (imageSubobject f : ModuleCat.{v} R) ⟶ N) [HasCokernel g] {x y : N} (l : L)
    (w : x = y + g (factorThruImageSubobject f l)) : cokernel.π g x = cokernel.π g y :=
  by
  subst w
  simp
#align Module.cokernel_π_image_subobject_ext ModuleCat.cokernel_π_imageSubobject_ext

end ModuleCat


import tactic.basic

structure protocol (sys_state_t : Type) :=
  (init : sys_state_t → Prop)
  (next : sys_state_t → sys_state_t → Prop)

namespace protocol

variable {sys_state_t : Type}

def reachable_in (proto : protocol sys_state_t) : ℕ → sys_state_t → Prop
| 0       := proto.init
| (n + 1) := (λ curr, (∃ prev, reachable_in n prev ∧ proto.next prev curr))

def reachable (proto : protocol sys_state_t) (s : sys_state_t) :=
  ∃ n, reachable_in proto n s

def reachable_in_from (proto : protocol sys_state_t) (src : sys_state_t) : ℕ → sys_state_t → Prop
| 0       := (λ dst, dst = src)
| (n + 1) := (λ dst, (∃ intermediate, reachable_in_from n intermediate ∧
                                      proto.next intermediate dst))

def reachable_from (proto : protocol sys_state_t) (src dst : sys_state_t) :=
  ∃ n, reachable_in_from proto src n dst

def invariant (proto : protocol sys_state_t) (predicate : sys_state_t → Prop) :=
  ∀ s, reachable proto s → predicate s

def stable (proto : protocol sys_state_t) (predicate : sys_state_t → Prop) :=
  ∀ s s', predicate s → proto.next s s' → predicate s'

lemma prove_invariant {predicate : sys_state_t → Prop}
  {proto : protocol sys_state_t}:
  proto.invariant predicate ↔
  (∀ s, proto.init s → predicate s) ∧
  proto.invariant (λ s, ∀ u, predicate s → proto.next s u → predicate u) :=
begin
split,
{ intro hyp,
  split,
  { intros s s_init, exact hyp s ⟨0, s_init⟩ },
  rintros s ⟨n, s_r_in_n⟩ u __ s_next_u,
  exact hyp u ⟨n.succ, s, s_r_in_n, s_next_u⟩ },
rintros ⟨holds_at_init, inductive_step⟩,
suffices : ∀ n s, proto.reachable_in n s → predicate s,
by { rintros s ⟨n, hn⟩, exact this n s hn },
intros n,
induction n with k hk,
{ intros s s_r_zero, exact holds_at_init s s_r_zero },
rintros s ⟨u, u_r_k, u_next_s⟩,
exact inductive_step u ⟨k, u_r_k⟩ s (hk u u_r_k) u_next_s
end

lemma reachable_from_reachable_is_reachable (proto : protocol sys_state_t) :
  ∀ s s', proto.reachable s → proto.reachable_from s s' → proto.reachable s' :=
begin
suffices : ∀ s, proto.reachable s → ∀ n s', proto.reachable_in_from s n s' → proto.reachable s',
by { rintros s s' reachable_s ⟨n, s'_reachable_from_s_in_n⟩, exact this s reachable_s n s' s'_reachable_from_s_in_n },
intros s reachable_s n,
induction n with k hk,
{ intros s' hs',
  have : s' = s := hs', rw this, exact reachable_s },
rintros s'' ⟨s', s'_reachable_from_s_in_k, s''_follows_s'⟩,
rcases (hk s' s'_reachable_from_s_in_k) with ⟨j, hj⟩,
exact ⟨j.succ, s', hj, s''_follows_s'⟩
end

lemma stability_applies_to_reachable_from (predicate : sys_state_t → Prop)
  (proto : protocol sys_state_t) :
  proto.stable predicate → ∀ s s', proto.reachable_from s s' → predicate s → predicate s' :=
begin
intro stability,
suffices : ∀ n s, predicate s → ∀ s', proto.reachable_in_from s n s' → predicate s',
by {
  rintros s s' ⟨n, hn⟩ predicate_at_s,
  exact this n s predicate_at_s s' hn },
intros n s predicate_at_s, induction n with k hk,
{ intros s' h, have : s' = s := h, rw this, exact predicate_at_s },
rintros s'' ⟨s', s'_reachable_in_k, s''_follows_s'⟩,
exact stability s' s'' (hk s' s'_reachable_in_k) s''_follows_s',
end

end protocol