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

! This file was ported from Lean 3 source module algebra.ring.idempotents
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Order.Basic
import Mathbin.Algebra.GroupPower.Basic
import Mathbin.Algebra.Ring.Defs
import Mathbin.Tactic.NthRewrite.Default

/-!
# Idempotents

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

This file defines idempotents for an arbitary multiplication and proves some basic results,
including:

* `is_idempotent_elem.mul_of_commute`: In a semigroup, the product of two commuting idempotents is
  an idempotent;
* `is_idempotent_elem.one_sub_iff`: In a (non-associative) ring, `p` is an idempotent if and only if
  `1-p` is an idempotent.
* `is_idempotent_elem.pow_succ_eq`: In a monoid `p ^ (n+1) = p` for `p` an idempotent and `n` a
  natural number.

## Tags

projection, idempotent
-/

variable {M N S M₀ M₁ R G G₀ : Type _}

variable [Mul M] [Monoid N] [Semigroup S] [MulZeroClass M₀] [MulOneClass M₁] [NonAssocRing R]
  [Group G] [CancelMonoidWithZero G₀]

#print IsIdempotentElem /-
/-- An element `p` is said to be idempotent if `p * p = p`
-/
def IsIdempotentElem (p : M) : Prop :=
  p * p = p
#align is_idempotent_elem IsIdempotentElem
-/

namespace IsIdempotentElem

#print IsIdempotentElem.of_isIdempotent /-
theorem of_isIdempotent [IsIdempotent M (· * ·)] (a : M) : IsIdempotentElem a :=
  IsIdempotent.idempotent a
#align is_idempotent_elem.of_is_idempotent IsIdempotentElem.of_isIdempotent
-/

#print IsIdempotentElem.eq /-
theorem eq {p : M} (h : IsIdempotentElem p) : p * p = p :=
  h
#align is_idempotent_elem.eq IsIdempotentElem.eq
-/

/- warning: is_idempotent_elem.mul_of_commute -> IsIdempotentElem.mul_of_commute is a dubious translation:
lean 3 declaration is
  forall {S : Type.{u1}} [_inst_3 : Semigroup.{u1} S] {p : S} {q : S}, (Commute.{u1} S (Semigroup.toHasMul.{u1} S _inst_3) p q) -> (IsIdempotentElem.{u1} S (Semigroup.toHasMul.{u1} S _inst_3) p) -> (IsIdempotentElem.{u1} S (Semigroup.toHasMul.{u1} S _inst_3) q) -> (IsIdempotentElem.{u1} S (Semigroup.toHasMul.{u1} S _inst_3) (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (Semigroup.toHasMul.{u1} S _inst_3)) p q))
but is expected to have type
  forall {S : Type.{u1}} [_inst_3 : Semigroup.{u1} S] {p : S} {q : S}, (Commute.{u1} S (Semigroup.toMul.{u1} S _inst_3) p q) -> (IsIdempotentElem.{u1} S (Semigroup.toMul.{u1} S _inst_3) p) -> (IsIdempotentElem.{u1} S (Semigroup.toMul.{u1} S _inst_3) q) -> (IsIdempotentElem.{u1} S (Semigroup.toMul.{u1} S _inst_3) (HMul.hMul.{u1, u1, u1} S S S (instHMul.{u1} S (Semigroup.toMul.{u1} S _inst_3)) p q))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.mul_of_commute IsIdempotentElem.mul_of_commuteₓ'. -/
theorem mul_of_commute {p q : S} (h : Commute p q) (h₁ : IsIdempotentElem p)
    (h₂ : IsIdempotentElem q) : IsIdempotentElem (p * q) := by
  rw [IsIdempotentElem, mul_assoc, ← mul_assoc q, ← h.eq, mul_assoc p, h₂.eq, ← mul_assoc, h₁.eq]
#align is_idempotent_elem.mul_of_commute IsIdempotentElem.mul_of_commute

/- warning: is_idempotent_elem.zero -> IsIdempotentElem.zero is a dubious translation:
lean 3 declaration is
  forall {M₀ : Type.{u1}} [_inst_4 : MulZeroClass.{u1} M₀], IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) (OfNat.ofNat.{u1} M₀ 0 (OfNat.mk.{u1} M₀ 0 (Zero.zero.{u1} M₀ (MulZeroClass.toHasZero.{u1} M₀ _inst_4))))
but is expected to have type
  forall {M₀ : Type.{u1}} [_inst_4 : MulZeroClass.{u1} M₀], IsIdempotentElem.{u1} M₀ (MulZeroClass.toMul.{u1} M₀ _inst_4) (OfNat.ofNat.{u1} M₀ 0 (Zero.toOfNat0.{u1} M₀ (MulZeroClass.toZero.{u1} M₀ _inst_4)))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.zero IsIdempotentElem.zeroₓ'. -/
theorem zero : IsIdempotentElem (0 : M₀) :=
  MulZeroClass.mul_zero _
#align is_idempotent_elem.zero IsIdempotentElem.zero

/- warning: is_idempotent_elem.one -> IsIdempotentElem.one is a dubious translation:
lean 3 declaration is
  forall {M₁ : Type.{u1}} [_inst_5 : MulOneClass.{u1} M₁], IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) (OfNat.ofNat.{u1} M₁ 1 (OfNat.mk.{u1} M₁ 1 (One.one.{u1} M₁ (MulOneClass.toHasOne.{u1} M₁ _inst_5))))
but is expected to have type
  forall {M₁ : Type.{u1}} [_inst_5 : MulOneClass.{u1} M₁], IsIdempotentElem.{u1} M₁ (MulOneClass.toMul.{u1} M₁ _inst_5) (OfNat.ofNat.{u1} M₁ 1 (One.toOfNat1.{u1} M₁ (MulOneClass.toOne.{u1} M₁ _inst_5)))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.one IsIdempotentElem.oneₓ'. -/
theorem one : IsIdempotentElem (1 : M₁) :=
  mul_one _
#align is_idempotent_elem.one IsIdempotentElem.one

/- warning: is_idempotent_elem.one_sub -> IsIdempotentElem.one_sub is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] {p : R}, (IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p) -> (IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toHasSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddGroupWithOne.toAddGroup.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6)))))) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (AddMonoidWithOne.toOne.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6))))))) p))
but is expected to have type
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] {p : R}, (IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p) -> (IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (AddGroupWithOne.toSub.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6)))) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (NonAssocRing.toOne.{u1} R _inst_6))) p))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.one_sub IsIdempotentElem.one_subₓ'. -/
theorem one_sub {p : R} (h : IsIdempotentElem p) : IsIdempotentElem (1 - p) := by
  rw [IsIdempotentElem, mul_sub, mul_one, sub_mul, one_mul, h.eq, sub_self, sub_zero]
#align is_idempotent_elem.one_sub IsIdempotentElem.one_sub

/- warning: is_idempotent_elem.one_sub_iff -> IsIdempotentElem.one_sub_iff is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] {p : R}, Iff (IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toHasSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddGroupWithOne.toAddGroup.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6)))))) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (AddMonoidWithOne.toOne.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6))))))) p)) (IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)
but is expected to have type
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] {p : R}, Iff (IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (AddGroupWithOne.toSub.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6)))) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (NonAssocRing.toOne.{u1} R _inst_6))) p)) (IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.one_sub_iff IsIdempotentElem.one_sub_iffₓ'. -/
@[simp]
theorem one_sub_iff {p : R} : IsIdempotentElem (1 - p) ↔ IsIdempotentElem p :=
  ⟨fun h => sub_sub_cancel 1 p ▸ h.one_sub, IsIdempotentElem.one_sub⟩
#align is_idempotent_elem.one_sub_iff IsIdempotentElem.one_sub_iff

/- warning: is_idempotent_elem.pow -> IsIdempotentElem.pow is a dubious translation:
lean 3 declaration is
  forall {N : Type.{u1}} [_inst_2 : Monoid.{u1} N] {p : N} (n : Nat), (IsIdempotentElem.{u1} N (MulOneClass.toHasMul.{u1} N (Monoid.toMulOneClass.{u1} N _inst_2)) p) -> (IsIdempotentElem.{u1} N (MulOneClass.toHasMul.{u1} N (Monoid.toMulOneClass.{u1} N _inst_2)) (HPow.hPow.{u1, 0, u1} N Nat N (instHPow.{u1, 0} N Nat (Monoid.Pow.{u1} N _inst_2)) p n))
but is expected to have type
  forall {N : Type.{u1}} [_inst_2 : Monoid.{u1} N] {p : N} (n : Nat), (IsIdempotentElem.{u1} N (MulOneClass.toMul.{u1} N (Monoid.toMulOneClass.{u1} N _inst_2)) p) -> (IsIdempotentElem.{u1} N (MulOneClass.toMul.{u1} N (Monoid.toMulOneClass.{u1} N _inst_2)) (HPow.hPow.{u1, 0, u1} N Nat N (instHPow.{u1, 0} N Nat (Monoid.Pow.{u1} N _inst_2)) p n))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.pow IsIdempotentElem.powₓ'. -/
theorem pow {p : N} (n : ℕ) (h : IsIdempotentElem p) : IsIdempotentElem (p ^ n) :=
  Nat.recOn n ((pow_zero p).symm ▸ one) fun n ih =>
    show p ^ n.succ * p ^ n.succ = p ^ n.succ
      by
      nth_rw 3 [← h.eq]
      rw [← sq, ← sq, ← pow_mul, ← pow_mul']
#align is_idempotent_elem.pow IsIdempotentElem.pow

/- warning: is_idempotent_elem.pow_succ_eq -> IsIdempotentElem.pow_succ_eq is a dubious translation:
lean 3 declaration is
  forall {N : Type.{u1}} [_inst_2 : Monoid.{u1} N] {p : N} (n : Nat), (IsIdempotentElem.{u1} N (MulOneClass.toHasMul.{u1} N (Monoid.toMulOneClass.{u1} N _inst_2)) p) -> (Eq.{succ u1} N (HPow.hPow.{u1, 0, u1} N Nat N (instHPow.{u1, 0} N Nat (Monoid.Pow.{u1} N _inst_2)) p (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))) p)
but is expected to have type
  forall {N : Type.{u1}} [_inst_2 : Monoid.{u1} N] {p : N} (n : Nat), (IsIdempotentElem.{u1} N (MulOneClass.toMul.{u1} N (Monoid.toMulOneClass.{u1} N _inst_2)) p) -> (Eq.{succ u1} N (HPow.hPow.{u1, 0, u1} N Nat N (instHPow.{u1, 0} N Nat (Monoid.Pow.{u1} N _inst_2)) p (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))) p)
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.pow_succ_eq IsIdempotentElem.pow_succ_eqₓ'. -/
theorem pow_succ_eq {p : N} (n : ℕ) (h : IsIdempotentElem p) : p ^ (n + 1) = p :=
  Nat.recOn n ((Nat.zero_add 1).symm ▸ pow_one p) fun n ih => by rw [pow_succ, ih, h.eq]
#align is_idempotent_elem.pow_succ_eq IsIdempotentElem.pow_succ_eq

/- warning: is_idempotent_elem.iff_eq_one -> IsIdempotentElem.iff_eq_one is a dubious translation:
lean 3 declaration is
  forall {G : Type.{u1}} [_inst_7 : Group.{u1} G] {p : G}, Iff (IsIdempotentElem.{u1} G (MulOneClass.toHasMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_7)))) p) (Eq.{succ u1} G p (OfNat.ofNat.{u1} G 1 (OfNat.mk.{u1} G 1 (One.one.{u1} G (MulOneClass.toHasOne.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_7))))))))
but is expected to have type
  forall {G : Type.{u1}} [_inst_7 : Group.{u1} G] {p : G}, Iff (IsIdempotentElem.{u1} G (MulOneClass.toMul.{u1} G (Monoid.toMulOneClass.{u1} G (DivInvMonoid.toMonoid.{u1} G (Group.toDivInvMonoid.{u1} G _inst_7)))) p) (Eq.{succ u1} G p (OfNat.ofNat.{u1} G 1 (One.toOfNat1.{u1} G (InvOneClass.toOne.{u1} G (DivInvOneMonoid.toInvOneClass.{u1} G (DivisionMonoid.toDivInvOneMonoid.{u1} G (Group.toDivisionMonoid.{u1} G _inst_7)))))))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.iff_eq_one IsIdempotentElem.iff_eq_oneₓ'. -/
@[simp]
theorem iff_eq_one {p : G} : IsIdempotentElem p ↔ p = 1 :=
  Iff.intro (fun h => mul_left_cancel ((mul_one p).symm ▸ h.Eq : p * p = p * 1)) fun h =>
    h.symm ▸ one
#align is_idempotent_elem.iff_eq_one IsIdempotentElem.iff_eq_one

/- warning: is_idempotent_elem.iff_eq_zero_or_one -> IsIdempotentElem.iff_eq_zero_or_one is a dubious translation:
lean 3 declaration is
  forall {G₀ : Type.{u1}} [_inst_8 : CancelMonoidWithZero.{u1} G₀] {p : G₀}, Iff (IsIdempotentElem.{u1} G₀ (MulZeroClass.toHasMul.{u1} G₀ (MulZeroOneClass.toMulZeroClass.{u1} G₀ (MonoidWithZero.toMulZeroOneClass.{u1} G₀ (CancelMonoidWithZero.toMonoidWithZero.{u1} G₀ _inst_8)))) p) (Or (Eq.{succ u1} G₀ p (OfNat.ofNat.{u1} G₀ 0 (OfNat.mk.{u1} G₀ 0 (Zero.zero.{u1} G₀ (MulZeroClass.toHasZero.{u1} G₀ (MulZeroOneClass.toMulZeroClass.{u1} G₀ (MonoidWithZero.toMulZeroOneClass.{u1} G₀ (CancelMonoidWithZero.toMonoidWithZero.{u1} G₀ _inst_8)))))))) (Eq.{succ u1} G₀ p (OfNat.ofNat.{u1} G₀ 1 (OfNat.mk.{u1} G₀ 1 (One.one.{u1} G₀ (MulOneClass.toHasOne.{u1} G₀ (MulZeroOneClass.toMulOneClass.{u1} G₀ (MonoidWithZero.toMulZeroOneClass.{u1} G₀ (CancelMonoidWithZero.toMonoidWithZero.{u1} G₀ _inst_8)))))))))
but is expected to have type
  forall {G₀ : Type.{u1}} [_inst_8 : CancelMonoidWithZero.{u1} G₀] {p : G₀}, Iff (IsIdempotentElem.{u1} G₀ (MulZeroClass.toMul.{u1} G₀ (MulZeroOneClass.toMulZeroClass.{u1} G₀ (MonoidWithZero.toMulZeroOneClass.{u1} G₀ (CancelMonoidWithZero.toMonoidWithZero.{u1} G₀ _inst_8)))) p) (Or (Eq.{succ u1} G₀ p (OfNat.ofNat.{u1} G₀ 0 (Zero.toOfNat0.{u1} G₀ (MonoidWithZero.toZero.{u1} G₀ (CancelMonoidWithZero.toMonoidWithZero.{u1} G₀ _inst_8))))) (Eq.{succ u1} G₀ p (OfNat.ofNat.{u1} G₀ 1 (One.toOfNat1.{u1} G₀ (Monoid.toOne.{u1} G₀ (MonoidWithZero.toMonoid.{u1} G₀ (CancelMonoidWithZero.toMonoidWithZero.{u1} G₀ _inst_8)))))))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.iff_eq_zero_or_one IsIdempotentElem.iff_eq_zero_or_oneₓ'. -/
@[simp]
theorem iff_eq_zero_or_one {p : G₀} : IsIdempotentElem p ↔ p = 0 ∨ p = 1 :=
  by
  refine'
    Iff.intro (fun h => or_iff_not_imp_left.mpr fun hp => _) fun h =>
      h.elim (fun hp => hp.symm ▸ zero) fun hp => hp.symm ▸ one
  exact mul_left_cancel₀ hp (h.trans (mul_one p).symm)
#align is_idempotent_elem.iff_eq_zero_or_one IsIdempotentElem.iff_eq_zero_or_one

/-! ### Instances on `subtype is_idempotent_elem` -/

section Instances

instance : Zero { p : M₀ // IsIdempotentElem p } where zero := ⟨0, zero⟩

/- warning: is_idempotent_elem.coe_zero -> IsIdempotentElem.coe_zero is a dubious translation:
lean 3 declaration is
  forall {M₀ : Type.{u1}} [_inst_4 : MulZeroClass.{u1} M₀], Eq.{succ u1} M₀ ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p)) M₀ (HasLiftT.mk.{succ u1, succ u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p)) M₀ (CoeTCₓ.coe.{succ u1, succ u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p)) M₀ (coeBase.{succ u1, succ u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p)) M₀ (coeSubtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p))))) (OfNat.ofNat.{u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p)) 0 (OfNat.mk.{u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p)) 0 (Zero.zero.{u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toHasMul.{u1} M₀ _inst_4) p)) (IsIdempotentElem.Subtype.hasZero.{u1} M₀ _inst_4))))) (OfNat.ofNat.{u1} M₀ 0 (OfNat.mk.{u1} M₀ 0 (Zero.zero.{u1} M₀ (MulZeroClass.toHasZero.{u1} M₀ _inst_4))))
but is expected to have type
  forall {M₀ : Type.{u1}} [_inst_4 : MulZeroClass.{u1} M₀], Eq.{succ u1} M₀ (Subtype.val.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toMul.{u1} M₀ _inst_4) p) (OfNat.ofNat.{u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toMul.{u1} M₀ _inst_4) p)) 0 (Zero.toOfNat0.{u1} (Subtype.{succ u1} M₀ (fun (p : M₀) => IsIdempotentElem.{u1} M₀ (MulZeroClass.toMul.{u1} M₀ _inst_4) p)) (IsIdempotentElem.instZeroSubtypeIsIdempotentElemToMul.{u1} M₀ _inst_4)))) (OfNat.ofNat.{u1} M₀ 0 (Zero.toOfNat0.{u1} M₀ (MulZeroClass.toZero.{u1} M₀ _inst_4)))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.coe_zero IsIdempotentElem.coe_zeroₓ'. -/
@[simp]
theorem coe_zero : ↑(0 : { p : M₀ // IsIdempotentElem p }) = (0 : M₀) :=
  rfl
#align is_idempotent_elem.coe_zero IsIdempotentElem.coe_zero

instance : One { p : M₁ // IsIdempotentElem p } where one := ⟨1, one⟩

/- warning: is_idempotent_elem.coe_one -> IsIdempotentElem.coe_one is a dubious translation:
lean 3 declaration is
  forall {M₁ : Type.{u1}} [_inst_5 : MulOneClass.{u1} M₁], Eq.{succ u1} M₁ ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p)) M₁ (HasLiftT.mk.{succ u1, succ u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p)) M₁ (CoeTCₓ.coe.{succ u1, succ u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p)) M₁ (coeBase.{succ u1, succ u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p)) M₁ (coeSubtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p))))) (OfNat.ofNat.{u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p)) 1 (OfNat.mk.{u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p)) 1 (One.one.{u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toHasMul.{u1} M₁ _inst_5) p)) (IsIdempotentElem.Subtype.hasOne.{u1} M₁ _inst_5))))) (OfNat.ofNat.{u1} M₁ 1 (OfNat.mk.{u1} M₁ 1 (One.one.{u1} M₁ (MulOneClass.toHasOne.{u1} M₁ _inst_5))))
but is expected to have type
  forall {M₁ : Type.{u1}} [_inst_5 : MulOneClass.{u1} M₁], Eq.{succ u1} M₁ (Subtype.val.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toMul.{u1} M₁ _inst_5) p) (OfNat.ofNat.{u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toMul.{u1} M₁ _inst_5) p)) 1 (One.toOfNat1.{u1} (Subtype.{succ u1} M₁ (fun (p : M₁) => IsIdempotentElem.{u1} M₁ (MulOneClass.toMul.{u1} M₁ _inst_5) p)) (IsIdempotentElem.instOneSubtypeIsIdempotentElemToMul.{u1} M₁ _inst_5)))) (OfNat.ofNat.{u1} M₁ 1 (One.toOfNat1.{u1} M₁ (MulOneClass.toOne.{u1} M₁ _inst_5)))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.coe_one IsIdempotentElem.coe_oneₓ'. -/
@[simp]
theorem coe_one : ↑(1 : { p : M₁ // IsIdempotentElem p }) = (1 : M₁) :=
  rfl
#align is_idempotent_elem.coe_one IsIdempotentElem.coe_one

instance : HasCompl { p : R // IsIdempotentElem p } :=
  ⟨fun p => ⟨1 - p, p.Prop.one_sub⟩⟩

/- warning: is_idempotent_elem.coe_compl -> IsIdempotentElem.coe_compl is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] (p : Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)), Eq.{succ u1} R ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (HasLiftT.mk.{succ u1, succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (CoeTCₓ.coe.{succ u1, succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (coeBase.{succ u1, succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (coeSubtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p))))) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasCompl.{u1} R _inst_6) p)) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (SubNegMonoid.toHasSub.{u1} R (AddGroup.toSubNegMonoid.{u1} R (AddGroupWithOne.toAddGroup.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6)))))) (OfNat.ofNat.{u1} R 1 (OfNat.mk.{u1} R 1 (One.one.{u1} R (AddMonoidWithOne.toOne.{u1} R (AddGroupWithOne.toAddMonoidWithOne.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6))))))) ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (HasLiftT.mk.{succ u1, succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (CoeTCₓ.coe.{succ u1, succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (coeBase.{succ u1, succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) R (coeSubtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p))))) p))
but is expected to have type
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] (p : Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)), Eq.{succ u1} R (Subtype.val.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instHasComplSubtypeIsIdempotentElemToMulToNonUnitalNonAssocRing.{u1} R _inst_6) p)) (HSub.hSub.{u1, u1, u1} R R R (instHSub.{u1} R (AddGroupWithOne.toSub.{u1} R (AddCommGroupWithOne.toAddGroupWithOne.{u1} R (NonAssocRing.toAddCommGroupWithOne.{u1} R _inst_6)))) (OfNat.ofNat.{u1} R 1 (One.toOfNat1.{u1} R (NonAssocRing.toOne.{u1} R _inst_6))) (Subtype.val.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p) p))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.coe_compl IsIdempotentElem.coe_complₓ'. -/
@[simp]
theorem coe_compl (p : { p : R // IsIdempotentElem p }) : ↑(pᶜ) = (1 : R) - ↑p :=
  rfl
#align is_idempotent_elem.coe_compl IsIdempotentElem.coe_compl

/- warning: is_idempotent_elem.compl_compl -> IsIdempotentElem.compl_compl is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] (p : Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)), Eq.{succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasCompl.{u1} R _inst_6) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasCompl.{u1} R _inst_6) p)) p
but is expected to have type
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R] (p : Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)), Eq.{succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instHasComplSubtypeIsIdempotentElemToMulToNonUnitalNonAssocRing.{u1} R _inst_6) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instHasComplSubtypeIsIdempotentElemToMulToNonUnitalNonAssocRing.{u1} R _inst_6) p)) p
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.compl_compl IsIdempotentElem.compl_complₓ'. -/
@[simp]
theorem compl_compl (p : { p : R // IsIdempotentElem p }) : pᶜᶜ = p :=
  Subtype.ext <| sub_sub_cancel _ _
#align is_idempotent_elem.compl_compl IsIdempotentElem.compl_compl

/- warning: is_idempotent_elem.zero_compl -> IsIdempotentElem.zero_compl is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R], Eq.{succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasCompl.{u1} R _inst_6) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 0 (OfNat.mk.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 0 (Zero.zero.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasZero.{u1} R (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))))))) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 1 (OfNat.mk.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 1 (One.one.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasOne.{u1} R (MulZeroOneClass.toMulOneClass.{u1} R (NonAssocSemiring.toMulZeroOneClass.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R _inst_6)))))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R], Eq.{succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instHasComplSubtypeIsIdempotentElemToMulToNonUnitalNonAssocRing.{u1} R _inst_6) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) 0 (Zero.toOfNat0.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instZeroSubtypeIsIdempotentElemToMul.{u1} R (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6))))))) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) 1 (One.toOfNat1.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instOneSubtypeIsIdempotentElemToMul.{u1} R (MulZeroOneClass.toMulOneClass.{u1} R (NonAssocSemiring.toMulZeroOneClass.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R _inst_6))))))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.zero_compl IsIdempotentElem.zero_complₓ'. -/
@[simp]
theorem zero_compl : (0 : { p : R // IsIdempotentElem p })ᶜ = 1 :=
  Subtype.ext <| sub_zero _
#align is_idempotent_elem.zero_compl IsIdempotentElem.zero_compl

/- warning: is_idempotent_elem.one_compl -> IsIdempotentElem.one_compl is a dubious translation:
lean 3 declaration is
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R], Eq.{succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasCompl.{u1} R _inst_6) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 1 (OfNat.mk.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 1 (One.one.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasOne.{u1} R (MulZeroOneClass.toMulOneClass.{u1} R (NonAssocSemiring.toMulZeroOneClass.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R _inst_6)))))))) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 0 (OfNat.mk.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) 0 (Zero.zero.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (Distrib.toHasMul.{u1} R (NonUnitalNonAssocSemiring.toDistrib.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))) p)) (IsIdempotentElem.Subtype.hasZero.{u1} R (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)))))))
but is expected to have type
  forall {R : Type.{u1}} [_inst_6 : NonAssocRing.{u1} R], Eq.{succ u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (HasCompl.compl.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instHasComplSubtypeIsIdempotentElemToMulToNonUnitalNonAssocRing.{u1} R _inst_6) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) 1 (One.toOfNat1.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instOneSubtypeIsIdempotentElemToMul.{u1} R (MulZeroOneClass.toMulOneClass.{u1} R (NonAssocSemiring.toMulZeroOneClass.{u1} R (NonAssocRing.toNonAssocSemiring.{u1} R _inst_6))))))) (OfNat.ofNat.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) 0 (Zero.toOfNat0.{u1} (Subtype.{succ u1} R (fun (p : R) => IsIdempotentElem.{u1} R (NonUnitalNonAssocRing.toMul.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6)) p)) (IsIdempotentElem.instZeroSubtypeIsIdempotentElemToMul.{u1} R (NonUnitalNonAssocSemiring.toMulZeroClass.{u1} R (NonUnitalNonAssocRing.toNonUnitalNonAssocSemiring.{u1} R (NonAssocRing.toNonUnitalNonAssocRing.{u1} R _inst_6))))))
Case conversion may be inaccurate. Consider using '#align is_idempotent_elem.one_compl IsIdempotentElem.one_complₓ'. -/
@[simp]
theorem one_compl : (1 : { p : R // IsIdempotentElem p })ᶜ = 0 :=
  Subtype.ext <| sub_self _
#align is_idempotent_elem.one_compl IsIdempotentElem.one_compl

end Instances

end IsIdempotentElem


def head {α} : (as : List α) → as ≠ [] → α
| [],    h => absurd rfl h
| a::as, _ => a

theorem head_cons {α} (a : α) (as : List α) : head (a::as) (fun h => List.noConfusion h) = a :=
rfl

theorem head_cons' {α} (a : α) (as : List α) (h : a::as ≠ []) : head (a::as) h = a :=
rfl


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Mario Carneiro, Johan Commelin, Amelia Livingston, Anne Baanen
-/
import ring_theory.ideal.operations
import ring_theory.localization.basic

/-!
# Ideals in localizations of commutative rings

## Implementation notes

See `src/ring_theory/localization/basic.lean` for a design overview.

## Tags
localization, ring localization, commutative ring localization, characteristic predicate,
commutative ring, field of fractions
-/
variables {R : Type*} [comm_ring R] (M : submonoid R) (S : Type*) [comm_ring S]
variables [algebra R S] {P : Type*} [comm_ring P]
namespace is_localization
variables [is_localization M S]

section ideals

variables (M) (S)
include M

/-- Explicit characterization of the ideal given by `ideal.map (algebra_map R S) I`.
In practice, this ideal differs only in that the carrier set is defined explicitly.
This definition is only meant to be used in proving `mem_map_to_map_iff`,
and any proof that needs to refer to the explicit carrier set should use that theorem. -/
private def map_ideal (I : ideal R) : ideal S :=
{ carrier := { z : S | ∃ x : I × M, z * algebra_map R S x.2 = algebra_map R S x.1},
  zero_mem' := ⟨⟨0, 1⟩, by simp⟩,
  add_mem' := begin
    rintros a b ⟨a', ha⟩ ⟨b', hb⟩,
    use ⟨a'.2 * b'.1 + b'.2 * a'.1, I.add_mem (I.mul_mem_left _ b'.1.2) (I.mul_mem_left _ a'.1.2)⟩,
    use a'.2 * b'.2,
    simp only [ring_hom.map_add, submodule.coe_mk, submonoid.coe_mul, ring_hom.map_mul],
    rw [add_mul, ← mul_assoc a, ha, mul_comm (algebra_map R S a'.2) (algebra_map R S b'.2),
        ← mul_assoc b, hb],
    ring
  end,
  smul_mem' := begin
    rintros c x ⟨x', hx⟩,
    obtain ⟨c', hc⟩ := is_localization.surj M c,
    use ⟨c'.1 * x'.1, I.mul_mem_left c'.1 x'.1.2⟩,
    use c'.2 * x'.2,
    simp only [←hx, ←hc, smul_eq_mul, submodule.coe_mk, submonoid.coe_mul, ring_hom.map_mul],
    ring
  end }

theorem mem_map_algebra_map_iff {I : ideal R} {z} :
  z ∈ ideal.map (algebra_map R S) I ↔ ∃ x : I × M, z * algebra_map R S x.2 = algebra_map R S x.1 :=
begin
  split,
  { change _ → z ∈ map_ideal M S I,
    refine λ h, ideal.mem_Inf.1 h (λ z hz, _),
    obtain ⟨y, hy⟩ := hz,
    use ⟨⟨⟨y, hy.left⟩, 1⟩, by simp [hy.right]⟩ },
  { rintros ⟨⟨a, s⟩, h⟩,
    rw [← ideal.unit_mul_mem_iff_mem _ (map_units S s), mul_comm],
    exact h.symm ▸ ideal.mem_map_of_mem _ a.2 }
end

theorem map_comap (J : ideal S) :
  ideal.map (algebra_map R S) (ideal.comap (algebra_map R S) J) = J :=
le_antisymm (ideal.map_le_iff_le_comap.2 le_rfl) $ λ x hJ,
begin
  obtain ⟨r, s, hx⟩ := mk'_surjective M x,
  rw ←hx at ⊢ hJ,
  exact ideal.mul_mem_right _ _ (ideal.mem_map_of_mem _ (show (algebra_map R S) r ∈ J, from
    mk'_spec S r s ▸ J.mul_mem_right ((algebra_map R S) s) hJ)),
end

theorem comap_map_of_is_prime_disjoint (I : ideal R) (hI : I.is_prime)
  (hM : disjoint (M : set R) I) :
  ideal.comap (algebra_map R S) (ideal.map (algebra_map R S) I) = I :=
begin
  refine le_antisymm (λ a ha, _) ideal.le_comap_map,
  rw [ideal.mem_comap, mem_map_algebra_map_iff M S] at ha,
  obtain ⟨⟨b, s⟩, h⟩ := ha,
  have : (algebra_map R S) (a * ↑s - b) = 0 := by simpa [sub_eq_zero] using h,
  rw [← (algebra_map R S).map_zero, eq_iff_exists M S] at this,
  obtain ⟨c, hc⟩ := this,
  have : a * s ∈ I,
  { rw zero_mul at hc,
    let this : (a * ↑s - ↑b) * ↑c ∈ I := hc.symm ▸ I.zero_mem,
    cases hI.mem_or_mem this with h1 h2,
    { simpa using I.add_mem h1 b.2 },
    { exfalso,
      refine hM ⟨c.2, h2⟩ } },
  cases hI.mem_or_mem this with h1 h2,
  { exact h1 },
  { exfalso,
    refine hM ⟨s.2, h2⟩ }
end

/-- If `S` is the localization of `R` at a submonoid, the ordering of ideals of `S` is
embedded in the ordering of ideals of `R`. -/
def order_embedding : ideal S ↪o ideal R :=
{ to_fun := λ J, ideal.comap (algebra_map R S) J,
  inj'   := function.left_inverse.injective (map_comap M S),
  map_rel_iff'   := λ J₁ J₂, ⟨λ hJ, (map_comap M S) J₁ ▸ (map_comap M S) J₂ ▸ ideal.map_mono hJ,
    ideal.comap_mono⟩ }

/-- If `R` is a ring, then prime ideals in the localization at `M`
correspond to prime ideals in the original ring `R` that are disjoint from `M`.
This lemma gives the particular case for an ideal and its comap,
see `le_rel_iso_of_prime` for the more general relation isomorphism -/
lemma is_prime_iff_is_prime_disjoint (J : ideal S) :
  J.is_prime ↔ (ideal.comap (algebra_map R S) J).is_prime ∧
    disjoint (M : set R) ↑(ideal.comap (algebra_map R S) J) :=
begin
  split,
  { refine λ h, ⟨⟨_, _⟩, λ m hm,
      h.ne_top (ideal.eq_top_of_is_unit_mem _ hm.2 (map_units S ⟨m, hm.left⟩))⟩,
    { refine λ hJ, h.ne_top _,
      rw [eq_top_iff, ← (order_embedding M S).le_iff_le],
      exact le_of_eq hJ.symm },
    { intros x y hxy,
      rw [ideal.mem_comap, ring_hom.map_mul] at hxy,
      exact h.mem_or_mem hxy } },
  { refine λ h, ⟨λ hJ, h.left.ne_top (eq_top_iff.2 _), _⟩,
    { rwa [eq_top_iff, ← (order_embedding M S).le_iff_le] at hJ },
    { intros x y hxy,
      obtain ⟨a, s, ha⟩ := mk'_surjective M x,
      obtain ⟨b, t, hb⟩ := mk'_surjective M y,
      have : mk' S (a * b) (s * t) ∈ J := by rwa [mk'_mul, ha, hb],
      rw [mk'_mem_iff, ← ideal.mem_comap] at this,
      replace this := h.left.mem_or_mem this,
      rw [ideal.mem_comap, ideal.mem_comap] at this,
      rwa [← ha, ← hb, mk'_mem_iff, mk'_mem_iff] } }
end

/-- If `R` is a ring, then prime ideals in the localization at `M`
correspond to prime ideals in the original ring `R` that are disjoint from `M`.
This lemma gives the particular case for an ideal and its map,
see `le_rel_iso_of_prime` for the more general relation isomorphism, and the reverse implication -/

/-- If `R` is a ring, then prime ideals in the localization at `M`
correspond to prime ideals in the original ring `R` that are disjoint from `M` -/
def order_iso_of_prime :
  {p : ideal S // p.is_prime} ≃o {p : ideal R // p.is_prime ∧ disjoint (M : set R) ↑p} :=
{ to_fun := λ p, ⟨ideal.comap (algebra_map R S) p.1,
                  (is_prime_iff_is_prime_disjoint M S p.1).1 p.2⟩,
  inv_fun := λ p, ⟨ideal.map (algebra_map R S) p.1,
                   is_prime_of_is_prime_disjoint M S p.1 p.2.1 p.2.2⟩,
  left_inv := λ J, subtype.eq (map_comap M S J),
  right_inv := λ I, subtype.eq (comap_map_of_is_prime_disjoint M S I.1 I.2.1 I.2.2),
  map_rel_iff' := λ I I', ⟨λ h, (show I.val ≤ I'.val,
    from (map_comap M S I.val) ▸ (map_comap M S I'.val) ▸ (ideal.map_mono h)), λ h x hx, h hx⟩ }

/-- `quotient_map` applied to maximal ideals of a localization is `surjective`.
  The quotient by a maximal ideal is a field, so inverses to elements already exist,
  and the localization necessarily maps the equivalence class of the inverse in the localization -/
lemma surjective_quotient_map_of_maximal_of_localization {I : ideal S} [I.is_prime] {J : ideal R}
  {H : J ≤ I.comap (algebra_map R S)} (hI : (I.comap (algebra_map R S)).is_maximal) :
  function.surjective (I.quotient_map (algebra_map R S) H) :=
begin
  intro s,
  obtain ⟨s, rfl⟩ := ideal.quotient.mk_surjective s,
  obtain ⟨r, ⟨m, hm⟩, rfl⟩ := mk'_surjective M s,
  by_cases hM : (ideal.quotient.mk (I.comap (algebra_map R S))) m = 0,
  { have : I = ⊤,
    { rw ideal.eq_top_iff_one,
      rw [ideal.quotient.eq_zero_iff_mem, ideal.mem_comap] at hM,
      convert I.mul_mem_right (mk' S (1 : R) ⟨m, hm⟩) hM,
      rw [← mk'_eq_mul_mk'_one, mk'_self] },
    exact ⟨0, eq_comm.1 (by simp [ideal.quotient.eq_zero_iff_mem, this])⟩ },
  { rw ideal.quotient.maximal_ideal_iff_is_field_quotient at hI,
    obtain ⟨n, hn⟩ := hI.3 hM,
    obtain ⟨rn, rfl⟩ := ideal.quotient.mk_surjective n,
    refine ⟨(ideal.quotient.mk J) (r * rn), _⟩,
    -- The rest of the proof is essentially just algebraic manipulations to prove the equality
    rw ← ring_hom.map_mul at hn,
    replace hn := congr_arg (ideal.quotient_map I (algebra_map R S) le_rfl) hn,
    simp only [ring_hom.map_one, ideal.quotient_map_mk, ring_hom.map_mul] at hn,
    rw [ideal.quotient_map_mk, ← sub_eq_zero, ← ring_hom.map_sub,
      ideal.quotient.eq_zero_iff_mem, ← ideal.quotient.eq_zero_iff_mem, ring_hom.map_sub,
      sub_eq_zero, mk'_eq_mul_mk'_one],
    simp only [mul_eq_mul_left_iff, ring_hom.map_mul],
    exact or.inl (mul_left_cancel₀ (λ hn, hM (ideal.quotient.eq_zero_iff_mem.2
      (ideal.mem_comap.2 (ideal.quotient.eq_zero_iff_mem.1 hn)))) (trans hn
      (by rw [← ring_hom.map_mul, ← mk'_eq_mul_mk'_one, mk'_self, ring_hom.map_one]))) }
end

end ideals

end is_localization


-- Copyright 2022-2023 VMware, Inc.
-- SPDX-License-Identifier: BSD-2-Clause

import .operators
import algebra.group.defs
import algebra.group.pi
import algebra.ring.basic
import tactic.abel

/-!
# Linearity, differentiation, and integration

This file extends DBSP with two key operators: differentiation and integration.
These are based on the idea of linearity, a property of streams over Abelian
(commutative) groups.
-/

variables {a : Type} [add_comm_group a].
variables {b : Type} [add_comm_group b].
variables {c : Type} [add_comm_group c].

instance stream_group : add_comm_group (stream a) := by { unfold stream, apply_instance }.

/-- An operator `S` is linear if S (x + y) = S x + S y; that is, it should be a
homomorphism between `stream a` and `stream b`. -/
def linear (S: operator a b) :=
  -- note this is phrased in the stream group
  (∀ x y, S (x + y) = S x + S y).

-- for symmetry with the other properties derived from linear (and to avoid
-- directly depending on this definition)
lemma linear_add {S: operator a b} (h: linear S) :
  ∀ s1 s2, S (s1 + s2) = S s1 + S s2 := h.

lemma linear_zero {S: operator a b} (h: linear S) :
  S 0 = 0 :=
begin
  have h0 := h 0 0,
  simp at h0,
  apply h0,
end

lemma linear_neg {S: operator a b} (h: linear S) :
  ∀ s, S (-s) = -S s :=
begin
  intros s,
  have h0 := h (-s) s, simp at h0,
  rw (linear_zero h) at h0,
  apply add_eq_zero_iff_eq_neg.mp, rw<- h0,
end

lemma linear_sub {S: operator a b} (h: linear S) :
  ∀ s1 s2, S (s1 - s2) = S s1 - S s2 :=
begin
  intros,
  repeat { rw sub_eq_add_neg },
  rw h,
  rw (linear_neg h),
end

theorem lifted_linear (f: a → b) :
  (∀ x y, f (x + y) = f x + f y) →
  linear (lifting f) :=
begin
  intros hlin s1 s2, funext s, simp,
  rw hlin,
end

/-- LTI stands for linear time invariant. -/
def lti (S: operator a b) :=
  linear S ∧ time_invariant S.

-- a weak zero-preservation at only t=0
lemma lti_operator_zpp (S: operator a b) :
  lti S → S 0 0 = 0 := λ h, time_invariant_0_0 S h.2.

/-- A function of two arguments is bilinear if it is linear in each argument
separately (holding the other constant). A classic example is multiplication. -/
def bilinear (f: a → b → c) :=
  -- linear in each argument, separately
  (∀ x1 x2 y, f (x1 + x2) y = f x1 y + f x2 y) ∧
  (∀ x y1 y2, f x (y1 + y2) = f x y1 + f x y2).

lemma bilinear_sub_1 {f: operator2 a b c} (hblin: bilinear f) :
  ∀ x1 x2 y, f (x1 - x2) y = f x1 y - f x2 y :=
begin
  intros _ _ _,
  have h: linear (λ x, f x y) := by {
    unfold linear,
    intros _ _,
    apply hblin.1,
  },
  apply (linear_sub h),
end

lemma bilinear_sub_2 {f: operator2 a b c} (hblin: bilinear f) :
  ∀ x y1 y2, f x (y1 - y2) = f x y1 - f x y2 :=
begin
  intros _ _ _,
  have h: linear (f x) := hblin.2 x,
  apply (linear_sub h),
end

theorem lifting_bilinear (f: a → b → c) :
  bilinear f → bilinear ↑²f :=
begin
  intros hf, unfold lifting2,
  split,
  { intros x1 x2 z,
    funext t, simp,
    apply hf.1, },
  { intros x1 x2 z,
    funext t, simp,
    apply hf.2, },
end

-- note that this is for the specific group ℤ
theorem mul_Z_bilinear :
  bilinear (lifting2 (λ (z1 z2 : ℤ), z1 * z2)) :=
begin
  apply lifting_bilinear,
  split; intros _ _ _; ring_nf,
end

-- NOTE: it is important to take {a: Type} since we don't want to assume the
-- group and ring structures over a separately, we need the group structure to
-- be the one from the ring
theorem mul_ring_bilinear {a: Type} [ring a] :
  bilinear (@lifting2 a a a (λ (z1 z2 : a), z1 * z2)) :=
begin
  apply lifting_bilinear,
  split; intros _ _ _; ring_nf,
  { rw right_distrib, },
  { rw left_distrib, },
end

/--

A "feedback" circuit that keeps adding its output from the previous time step,
defined using a fixpoint. To be well-defined, `S` must be causal.

```
                ┌─────┐
 s ────▶ + ────▶│  S  │───▶ α
         ▲      └─────┘
         │         │
      ┌─────┐      │
      │ z⁻¹ │◀─────┘
      └─────┘
```
-/
def feedback (S: operator a a) : operator a a :=
  λ s, fix (λ α, S (s + delay α)).

theorem feedback_strict {S : operator a a}
  (hcausal : causal S) (s : stream a) :
  strict (λ (α : stream a), S (s + delay α)) :=
begin
  apply (feedback_ckt_body_strict _ delay_strict (λ s t, S (s + t))),
  rw causal2,
  introv h1 h2,
  apply hcausal,
  intros i hle, simp,
  rw [h1, h2]; omega
end

/-- As long as `S` is [causal], the body of the feedback loop is strict and
[feedback] can be unfolded according to its recursive definition. -/
theorem feedback_unfold (S: operator a a) :
  causal S →
  ∀ s, feedback S s = S (s + delay (feedback S s)) :=
begin
  intros hcausal s,
  unfold feedback,
  apply fix_eq,
  apply (feedback_strict hcausal),
end

lemma delay_linear : linear (@delay a _) :=
begin
  intros x y,
  funext t,
  unfold delay, simp,
  by_cases h_t : t = 0,
  { repeat { rw (if_pos h_t) }, simp, },
  { repeat { rw (if_neg h_t) } },
end

theorem add_linear :
  linear (uncurry_op ((+) : stream a → stream a → stream a)) :=
begin
  intros x y,
  funext t,
  unfold uncurry_op delay, simp,
  abel,
end

lemma agree_upto_respects_add (s1 s2 s1' s2': stream a) (n: ℕ) :
  s1 ==n== s1' →
  s2 ==n== s2' →
  (s1 + s2) ==n== (s1' + s2') :=
begin
  intros h1 h2,
  intros t hle, simp,
  rw [h1, h2]; assumption,
end

lemma agree_upto_respects_sub (s1 s2 s1' s2': stream a) (n: ℕ) :
  s1 ==n== s1' →
  s2 ==n== s2' →
  (s1 - s2) ==n== (s1' - s2') :=
begin
  intros h1 h2,
  intros t hle, simp,
  rw [h1, h2]; assumption,
end

-- TODO: can we give a general characterization of time invariance of fixpoints?

theorem feedback_time_invariant (S: operator a a) :
  causal S → time_invariant S →
  time_invariant (feedback S) :=
begin
  intros hcausal hti,
  intros s,
  rw agree_everywhere_eq,
  intros n,
  induction n with n,
  { rw agree_upto_0,
    unfold feedback, simp,
    rw (time_invariant_t hti), simp,
  },
  { rw feedback_unfold; try { assumption },
    rw<- delay_linear,
    rw hti,
    apply delay_succ_upto,
    have h : s + feedback S (delay s) ==n== s + delay (feedback S s) := by {
      apply agree_upto_respects_add,
      reflexivity, assumption,
    },
    have heq := (causal_respects_agree_upto _ hcausal _ _ _ h),
    transitivity, assumption,
    rw<- (feedback_unfold _ hcausal s),
  },
end

theorem feedback_causal (S: operator a a) :
  causal S →
  causal (feedback S) :=
begin
  intros hcausal,
  have h := hcausal,
  rw causal_to_agree at h |-,
  introv heq,
  induction n with n,
  { rw [feedback_unfold _ hcausal s1,
        feedback_unfold _ hcausal s2],
    apply h,
    rw agree_upto_0 at heq |-, simp, assumption,
   },
  { rw [feedback_unfold _ hcausal s1,
        feedback_unfold _ hcausal s2],
    apply h,
    apply agree_upto_respects_add, assumption,
    apply delay_succ_upto,
    apply n_ih,
    apply agree_upto_weaken1, assumption, }
end

theorem feedback_linear (S: operator a a) :
  causal S → lti S →
  linear (feedback S) :=
begin
  intros hcausal hlti,
  cases hlti with hlin hti,
  intros s1 s2,
  symmetry, apply fix_unique,
  { apply (feedback_strict hcausal) },
  conv_lhs begin
    rw [feedback_unfold _ hcausal s1,
        feedback_unfold _ hcausal s2]
  end,
  repeat { rw hlin <|> rw delay_linear },
  abel,
end

theorem feedback_lti (S: operator a a) :
  causal S → lti S →
  lti (feedback S) :=
begin
  intros hcausal hlti,
  split,
  { apply feedback_linear; assumption, },
  { apply feedback_time_invariant; cases hlti; assumption },
end

/-- The derivative operator, written simply `D`, is a core operator in DBSP. It
"differentiates" a stream: `(D s)(t) = s(t) - s(t - 1)` (with `(D s)(0) = 0`),
producing a stream of what we can think of as changes, although both the input
and output are streams of a's.

Theorem names related to the derivative will use 'derivative'.
-/
def D : operator a a := λ s, s - delay s.

@[simp]
lemma derivative_causal : causal (@D a _) :=
begin
  rw causal_to_agree,
  unfold D,
  intros s s' t hagree,
  apply agree_upto_respects_sub,
  assumption,
  apply agree_upto_weaken1,
  apply delay_succ_upto, assumption,
end

lemma derivative_time_invariant : time_invariant (@D a _) :=
begin
  unfold time_invariant D,
  intros s,
  rw (linear_sub delay_linear),
end

lemma derivative_linear : linear (@D a _) :=
begin
  intros s1 s2,
  unfold D,
  funext s, simp,
  repeat { rw delay_linear },
  abel, simp,
  rw add_comm,
end

lemma derivative_lti : lti (@D a _) :=
begin
  split, apply derivative_linear, apply derivative_time_invariant,
end

/-- The integral operator is another fundamental operator in DBSP. It takes a
stream of changes and computes the sum of the changes so far; this is
implemented recursively with a fixpoint.

Theorem names will use 'integral' even though the operator is named I.
-/
def I : operator a a := feedback id.

protected lemma id_causal : causal (@id (stream a)) :=
begin
  unfold causal,
  intros s s' t heq,
  apply heq, omega,
end

protected lemma id_time_invariant : time_invariant (@id (stream a)) :=
begin
  unfold time_invariant,
  simp,
end

protected lemma id_lti : lti (@id (stream a)) :=
begin
  split,
  { intros s1 s2, simp },
  apply id_time_invariant,
end

@[simp]
lemma integral_causal : causal (@I a _) :=
begin
  apply feedback_causal,
  apply id_causal
end

theorem integral_lti : lti (@I a _) :=
begin
  unfold I,
  apply feedback_lti,
  { apply id_causal, },
  { apply id_lti, }
end

theorem integral_time_invariant : time_invariant (@I a _) :=
  integral_lti.2.

theorem integral_linear : linear (@I a _) :=
  integral_lti.1.

theorem integral_unfold :
  ∀ (s: stream a), I s = s + delay (I s) :=
begin
  intros s,
  unfold I,
  apply feedback_unfold,
  apply id_causal,
end

/-- The sum of `s[0] .. s[n-1]`. This is a closed form version of the integral
operator (offset by 1), as proven in [integral_sum_vals]. -/
def sum_vals (s: stream a) : ℕ → a
| 0 := 0
| (nat.succ n) := s n + sum_vals n.

@[simp]
lemma sum_vals_0 (s: stream a) : sum_vals s 0 = 0 := rfl.

@[simp]
lemma sum_vals_1 (s: stream a) : sum_vals s 1 = s 0 :=
begin
  unfold sum_vals, simp,
end

attribute [simp] sum_vals.equations._eqn_2.

/- The sum of an all-zero stream is zero. -/
lemma sum_vals_zero (s: stream a) :
  (∀ n, s n = 0) →
  ∀ (n:ℕ), sum_vals s n = 0 :=
begin
  intros hz n,
  induction n with n,
  { simp, },
  { simp, rw [hz, n_ih], abel, },
end

@[simp]
lemma integral_0 (s: stream a) : I s 0 = s 0 :=
begin
  rw integral_unfold, simp,
end

theorem integral_sum_vals (s: stream a) (n: ℕ) :
  I s n = sum_vals s n.succ :=
begin
  induction n with n,
  { simp, },
  { rw integral_unfold, simp,
    rw n_ih, refl, }
end

@[simp]
lemma integral_zpp : I (0: stream a) = 0 :=
begin
  funext t,
  rw integral_sum_vals, simp,
  rw sum_vals_zero, simp,
end

-- simple expression for the values of derivative s
@[simp]
lemma derivative_0 (s: stream a) :
  D s 0 = s 0 :=
begin
  unfold D; simp,
end

theorem derivative_difference_t (s: stream a) (t: ℕ) :
  0 < t →
  D s t = s t - s (t - 1) :=
begin
  intros hnz,
  unfold D delay, dsimp,
  rw if_neg, omega,
end

@[simp]
lemma derivative_zpp : D (0: stream a) = 0 :=
begin
  funext t, unfold D, simp,
end

lemma add_causal :
  causal (uncurry_op ((+) : operator2 a a a)) :=
begin
  unfold causal, introv heq,
  unfold uncurry_op, simp, rw heq, linarith,
end

lemma sum_causal (f g: operator a b) :
  causal f →
  causal g →
  causal (λ x, f x + g x) :=
begin
  unfold causal, intros hf hg, introv heq,
  simp, rw [hf, hg]; assumption,
end

lemma sum_causal_nested (f g: operator (stream a) (stream b)) :
  causal_nested f →
  causal_nested g →
  causal_nested (λ x, f x + g x) :=
begin
  unfold causal_nested, intros hf hg, introv heq,
  simp, rw [hf, hg]; assumption,
end

lemma sum_vals_succ_n (s: stream a) (t: ℕ) :
  sum_vals (D s) t.succ = s t :=
begin
  induction t with t,
  { unfold sum_vals D, simp, },
  simp only [sum_vals] at t_ih |-,
  rw derivative_difference_t; try { omega },
  rw t_ih, simp,
end

@[simp]
theorem derivative_integral (s: stream a) :
  I (D s) = s :=
begin
  funext t,
  rw integral_sum_vals,
  rw sum_vals_succ_n,
end

-- We can give an alternative proof based on the fact that I is the unique
-- fixpoint of α = α + z⁻¹ α.
private theorem derivative_integral_alt (s: stream a) :
  I (D s) = s :=
begin
  symmetry,
  simp [I, feedback],
  apply fix_unique,
  { apply feedback_ckt_body_strict,
    apply delay_strict,
    apply add_causal,  },
  { unfold D, abel, },
end

-- And yet another proof, based on linearity, the fixpoint equation, and time
-- invariance.
private theorem derivative_integral_alt2 (s: stream a) :
  I (D s) = s :=
begin
  unfold D,
  rw (linear_sub integral_linear),
  rw (integral_unfold s),
  rw integral_time_invariant, abel,
end

@[simp]
theorem integral_derivative (s: stream a) :
  D (I s) = s :=
begin
  unfold D,
  calc I s - delay (I s)
        = s + delay (I s) - delay (I s) : by { congr', apply integral_unfold, }
  ...   = s : by { rw add_sub_cancel, },
end

theorem derivative_integral_inverse (α s: stream a):
  α = I s ↔ D α = s :=
begin
  split,
  { intros h, subst α,
    rw integral_derivative, },
  { intros h, subst s,
    rw derivative_integral,
   }
end

lemma i_d_comp :
  I ∘ D = @id (stream a) :=
begin
  funext s, simp,
end

lemma d_i_comp :
  D ∘ I = @id (stream a) :=
begin
  funext s, simp,
end

theorem lifting_linear (f: a → b) :
  (∀ x y, f (x + y) = f x + f y)  →
  linear (↑↑f) :=
begin
  intros hlin,
  intros x y, ext t, simp, apply hlin,
end

-- convenience for generating lti theorems
theorem lifting_lti (f: a → b) :
  (∀ x y, f (x + y) = f x + f y)  →
  lti (↑↑ f) :=
begin
  intros hlin,
  split,
  { apply lifting_linear, assumption, },
  { apply lifting_time_invariant,
    have h0 := hlin 0 0, simp at h0,
    apply h0, },
end

lemma derivative_sprod (s1: stream a) (s2: stream b) :
  D (sprod (s1, s2)) = sprod (D s1, D s2) :=
begin
  funext t, simp,
  by_cases (t = 0),
  { subst t, simp, },
  { repeat { rw derivative_difference_t }; try { omega },
    simp,
  },
end

lemma integral_sprod (s1: stream a) (s2: stream b) :
  I (sprod (s1, s2)) = sprod (I s1, I s2) :=
begin
  funext t, simp,
  repeat { rw integral_sum_vals },
  simp,
  induction t,
  { simp, },
  { simp, rw t_ih, refl, },
end

lemma integral_lift_comm (f: a → b) (s: stream a) :
  (∀ x y, f (x + y) = f x + f y) →
  I (↑↑f s) = ↑↑f (I s) :=
begin
  intros hlin,
  funext t, simp,
  repeat { rw integral_sum_vals },
  induction t,
  { simp, },
  { simp at t_ih |-,
    rw [hlin, t_ih],
  }
end

lemma integral_fst_comm (s: stream (a × b)) :
  I (↑↑prod.fst s) = ↑↑prod.fst (I s) :=
begin
  apply integral_lift_comm,
  intros x y, simp,
end

lemma integral_snd_comm (s: stream (a × b)) :
  I (↑↑prod.snd s) = ↑↑prod.snd (I s) :=
begin
  apply integral_lift_comm,
  intros x y, simp,
end

-- #lint only doc_blame simp_nf