Zulip Chat Archive

Stream: new members

Topic: Lifting quotient predicate when defining reciprocal


Jackson Brough (Mar 05 2024 at 06:55):

Hello everyone! I have started working through Terence Tao's Analysis 1 in Lean, and I was able to use quotient types to construct the integers, but I'm having trouble defining reciprocal for the rational numbers.

The general problem seems to be utilizing a predicate over a quotient type in a proof about the underlying type. I thought it might be possible to use Quotient.rec to construct a function from some a:Qa : ℚ to the set of functions which send a proof that aa is nonzero to Q (making use of the fact that aa is nonzero to properly flip the fraction). That actually worked for the function definition, but now I'm very stuck on the proof that the function respects the equivalence relation. Is it possible to make use of predicates in this way when lifting a function to a quotient?

Two other people have had very similar questions:

Here's a #mwe. I converted the quotient integers I'm using to mathlib's integers, and it should be easy to convert back if I can get the mathlib version working.

import Mathlib

def NonZeroInteger := {a :  // a  0}

def RationalEquivalent : ( × NonZeroInteger)  ( × NonZeroInteger)  Prop
  | (a, b, _⟩), (c, d, _⟩) => a * d = c * b

theorem RationalEquivalent.reflexive :  a, RationalEquivalent a a :=
  λ _ => rfl

theorem RationalEquivalent.symmetric :  {a b}, RationalEquivalent a b  RationalEquivalent b a := by
  intro (a, b, _⟩) (c, d, _⟩) h_ab_cd
  have h_ab_cd' : a * d = c * b := h_ab_cd
  show c * b = a * d
  exact Eq.symm h_ab_cd'

theorem RationalEquivalent.transitive :  {a b c}, RationalEquivalent a b  RationalEquivalent b c  RationalEquivalent a c := by
  intro (a, b, b_nonzero⟩) (c, d, d_nonzero⟩) (e, f, _⟩)
  intro (h_ab_cd : a * d = c * b) (h_cd_ef : c * f = e * d)
  show a * f = e * b
  cases Int.decEq c 0 with
  | isTrue c_zero =>
    have ad_zero := c_zero  h_ab_cd
    rw [Int.zero_mul] at ad_zero
    have a_zero := Or.resolve_right (Int.eq_zero_or_eq_zero_of_mul_eq_zero ad_zero) d_nonzero
    have ed_zero := c_zero  h_cd_ef
    rw [Int.zero_mul] at ed_zero
    have e_zero := Or.resolve_right (Int.eq_zero_or_eq_zero_of_mul_eq_zero ed_zero.symm) d_nonzero
    simp [a_zero, e_zero, Int.mul_zero, Int.zero_mul]
  | isFalse c_nonzero =>
    have h_equal := calc
      (c * d) * (a * f) = (c * f) * (a * d) := by simp [Int.mul_assoc, Int.mul_comm, Int.mul_left_comm]
      _ = (e * d) * (a * d) := congrArg (. * _) h_cd_ef
      _ = (e * d) * (c * b) := congrArg (_ * .) h_ab_cd
      _ = (c * d) * (e * b) := by simp [Int.mul_assoc, Int.mul_comm, Int.mul_left_comm]
    have cd_nonzero := Int.mul_ne_zero c_nonzero d_nonzero
    exact Int.eq_of_mul_eq_mul_left cd_nonzero h_equal

theorem RationalEquivalent.is_equivalence : Equivalence RationalEquivalent :=
  { refl := RationalEquivalent.reflexive, symm := RationalEquivalent.symmetric, trans := RationalEquivalent.transitive }

instance instanceHasEquivRationalEquivalent : HasEquiv (Int × NonZeroInteger) where
  Equiv := RationalEquivalent

instance instanceSetoidRationalEquivalent : Setoid (Int × NonZeroInteger) where
  r := RationalEquivalent
  iseqv := RationalEquivalent.is_equivalence

@[simp] def RationalEquivalent.definition : (x  y) = RationalEquivalent x y := rfl

def Rational := Quotient instanceSetoidRationalEquivalent

instance : OfNat Rational n where
  ofNat := (Int.ofNat n, 1, by decide⟩)

def reciprocal (a : Rational) : a  0  Rational :=
  let reciprocal' :=
    λ (a :  × NonZeroInteger) =>
    λ (a_nonzero : a  (0 : Rational)) =>
    have a1_nonzero : a.1  0 := by
    { intro h
      have := calc
        a.1 * 1 = a.1 := Int.mul_one _
        _ = 0 := h
        _ = 0 * a.2.val := (Int.zero_mul _).symm
      exact a_nonzero (Quotient.sound this) }
    -- Couldn't get it to type check without being overly explicit
    Quotient.mk instanceSetoidRationalEquivalent (a.2.val, ({ val := a.1, property := a1_nonzero } : NonZeroInteger))
  Quotient.recOn a reciprocal' <| by
  -- How to close this goal? Use `Quotient.hrecOn`? Applying `heq_of_eq` doesn't seem to work
  skip

Johan Commelin (Mar 05 2024 at 07:25):

I think that whichever solution you reach, you'll always be happy to extract your

  let reciprocal' :=
    λ (a :  × NonZeroInteger) =>
    λ (a_nonzero : a  (0 : Rational)) =>
    have a1_nonzero : a.1  0 := by
    { intro h
      have := calc
        a.1 * 1 = a.1 := Int.mul_one _
        _ = 0 := h
        _ = 0 * a.2.val := (Int.zero_mul _).symm
      exact a_nonzero (Quotient.sound this) }

into a helper lemma.

Johan Commelin (Mar 05 2024 at 07:26):

And maybe you even want to phrase it as an iff?

Kevin Buzzard (Mar 05 2024 at 08:00):

The "party line" would be to define reciprocal on all rationals, and define the reciprocal of zero to be zero. If you don't like that then try it anyway, and define your reciprocal using it. Does this approach fix the problem in your case or just move it?

Kevin Buzzard (Mar 05 2024 at 08:01):

PS I would be tempted to make NonZeroInteger an abbrev rather than a def because it's just an abbreviation rather than something you want to develop an API for

Kevin Buzzard (Mar 05 2024 at 09:08):

theorem RationalEquivalent.symmetric :  {a b}, RationalEquivalent a b  RationalEquivalent b a := by
  intro (a, b, _⟩) (c, d, _⟩) h_ab_cd
  exact h_ab_cd.symm

or even

theorem RationalEquivalent.symmetric {a b} (h : RationalEquivalent a b) : RationalEquivalent b a :=
  h.symm

Kevin Buzzard (Mar 05 2024 at 09:23):

Here's an approach which makes it less painful:

def prereciprocal (x :  × NonZeroInteger) : Rational :=
  if h : x.1 = 0 then 0 else (x.2.1, x.1, h⟩)

@[simp]
lemma prereciprocal_zero (x :  × NonZeroInteger) (hx : x.1 = 0) : prereciprocal x = 0 := by
  simp [prereciprocal, hx]

@[simp]
lemma prereciprocal_ne_zero (x :  × NonZeroInteger) (hx : x.1  0) :
    prereciprocal x = (x.2.1, x.1, hx⟩) := by
  simp [prereciprocal, hx]

def reciprocal : Rational  Rational :=
  Quotient.lift prereciprocal <| by
  intro a1, a2, ha b1, b2, hb (h : a1 * b2 = b1 * a2)
  rcases eq_or_ne a1 0 <;> rcases eq_or_ne b1 0 <;> simp_all
  rw [Quotient.eq]
  change a2 * b1  = b2 * a1
  polyrith

Kevin Buzzard (Mar 05 2024 at 09:28):

And if you like you can call that reciprocal' and define your reciprocal to be my reciprocal but with the random extra hypothesis that the input isn't 0.

Kevin Buzzard (Mar 05 2024 at 10:12):

theorem RationalEquivalent.transitive :  {a b c}, RationalEquivalent a b  RationalEquivalent b c  RationalEquivalent a c := by
  intro (a, b, b_nonzero⟩) (c, d, d_nonzero⟩) (e, f, _⟩)
  intro (h_ab_cd : a * d = c * b) (h_cd_ef : c * f = e * d)
  apply mul_left_cancel₀ d_nonzero
  polyrith

Jackson Brough (Mar 08 2024 at 00:09):

Sorry it took me so long to reply! Thank you both for your suggestions, it's always really great to get code/proof suggestions from people with more experience. The transitivity and reciprocal'-respects proofs are both very clean.

Jackson Brough (Mar 08 2024 at 00:09):

I defined reciprocal using the "party line" method, and this definitely works. I am encouraged by the prereciprocal_ne_zero theorem; I think this should make proving theorems about reciprocal relatively painless.

Jackson Brough (Mar 08 2024 at 00:10):

Part of my motivation for asking was that I have another case with essentially the same problem. I'm trying to define conversions between the natural numbers and the positive integers (constructed using quotients) and then showing that they are inverses of each other. I tried to apply the same case split method but using an Option instead of zero, and this almost works. The issue is in the "_left_inverse" proofs at the very bottom. It's a bit silly, but is there a reason the simplifier can't close the goals?

Jackson Brough (Mar 08 2024 at 00:13):

Unfortunately my code sample is pretty large. I did replace any proofs longer than two lines with sorry for brevity. If I can get the last two proofs to type check, I'm satisfied with this approach, but is it possible to avoid the case split entirely?

Jackson Brough (Mar 08 2024 at 00:13):

import Mathlib

def IntegerEquivalent : ( × )  ( × )  Prop
  | (n, m), (k, l) => n + l = k + m

theorem IntegerEquivalent.reflexive :  x, IntegerEquivalent x x :=
  λ _ => rfl

theorem IntegerEquivalent.symmetric :  {x y}, IntegerEquivalent x y  IntegerEquivalent y x :=
  Eq.symm

theorem IntegerEquivalent.transitive :  {x y z}, IntegerEquivalent x y  IntegerEquivalent y z  IntegerEquivalent x z
  | (a, b), (c, d), (e, f), (h₁ : a + d = c + b), (h₂ : c + f = e + d) => by
    apply add_left_cancel (a := (c + d))
    linarith

theorem IntegerEquivalent.is_equivalence : Equivalence IntegerEquivalent :=
  { refl := IntegerEquivalent.reflexive, symm := IntegerEquivalent.symmetric, trans := IntegerEquivalent.transitive }

instance instanceHasEquivIntegerEquivalent : HasEquiv ( × ) where
  Equiv := IntegerEquivalent

instance instanceSetoidIntegerEquivalent : Setoid ( × ) where
  r := IntegerEquivalent
  iseqv := IntegerEquivalent.is_equivalence

def Integer := Quotient instanceSetoidIntegerEquivalent

namespace Integer

@[simp]
def ofNat (n : ) : Integer := (n, 0)

def ofNat_definition : ofNat n = Quotient.mk instanceSetoidIntegerEquivalent (n, 0) := rfl

instance Zero : Integer := (0, 0)

instance One : Integer := (1, 0)

instance : OfNat Integer n where
  ofNat := (n, 0)

instance : Coe Nat Integer := ofNat

theorem ofNat_injective : Function.Injective ofNat := by
  intro a b h
  rw [ Nat.add_zero a, Quotient.exact h, Nat.add_zero]

def add : Integer  Integer  Integer :=
  let add' := λ ((n, m) :  × ) ((k, l) :  × ) => (n + k, m + l)
  Quotient.map₂ add' <| by
  sorry

instance : Add Integer where add := add

@[simp] theorem add_definition : add a b = a + b := rfl

theorem add_commutative :  (a b : Integer), a + b = b + a := sorry

theorem add_zero :  (a : Integer), a + 0 = a := sorry

theorem zero_add (a : Integer) : 0 + a = a := by
  rw [add_commutative, add_zero]

def LessEqual (a b : Integer) : Prop :=  (n : ), a + n = b

instance : LE Integer where
  le := LessEqual

theorem ofNat_nonnegative (n : ) : n  (0 : Integer) :=
  Exists.intro n (zero_add n)

abbrev NonNegativeInteger := {a : Integer // a  0}

-- Didn't find any Nat lemmas that looked like this
theorem Nat.left_greater_equal_of_add_right_less_equal {n m k l : } : n + m = k + l  m  l  n  k := sorry

theorem Nat.right_greater_equal_of_add_left_less_equal {n m k l : } : n + m = k + l  n  k  m  l := sorry

-- Important stuff starts here

def preToNatural' :  ×   Option 
  | (n, m) => if n  m then some (n - m) else none

@[simp]
theorem preToNatural_none (x :  × ) (h : x.1 < x.2) : preToNatural' x = none := by
  have := not_le_of_gt h
  simp [preToNatural', not_le_of_gt h]

@[simp]
theorem preToNatural_some (x :  × ) (h : x.1  x.2) : preToNatural' x = some (x.1 - x.2) := by
  simp [preToNatural', h]

def toNatural' : Integer  Option  :=
  Quotient.lift preToNatural' <| by
  intro (n, m) (k, l) (h : n + l = k + m)
  simp [preToNatural', preToNatural_none, preToNatural_some]
  cases Decidable.em (m  n)
  <;> cases Decidable.em (l  k)
  <;> simp_all only [preToNatural', preToNatural_none, preToNatural_some, ite_true, ite_false]
  case inl.inl hnm hkl =>
    apply congrArg some
    apply Nat.add_right_cancel (m := m + l)
    rw [ Nat.add_assoc, Nat.sub_add_cancel, h,  Nat.sub_add_cancel hkl,
      Nat.add_assoc (k - l) l m, Nat.add_comm l m, Nat.sub_add_cancel]
    exact hkl
    exact hnm
  case inl.inr hnm hkl =>
    rw [Nat.add_comm k m] at h
    have := Nat.right_greater_equal_of_add_left_less_equal h.symm hnm
    exact absurd this hkl
  case inr.inl hnm hkl =>
    rw [Nat.add_comm n l] at h
    have := Nat.right_greater_equal_of_add_left_less_equal h hkl
    exact absurd this hnm

def toNatural : NonNegativeInteger  
  | (⟨a, a_nonnegative⟩) =>
    let o := toNatural' a
    have : Option.isSome o = true := by
    { have n, hn := a_nonnegative
      rw [zero_add, ofNat] at hn
      simp [Option.isSome, toNatural', o]
      rw [ hn, Quotient.lift_mk]
      rw [preToNatural']
      have : n  0 := Nat.zero_le n
      simp only [this, sub_zero, ite_true, Option.isSome] }
    match o with
    | some n => n

def fromNatural (n : ) : NonNegativeInteger :=
  n, ofNat_nonnegative n

theorem fromNatural_toNatural_left_inverse : Function.LeftInverse toNatural fromNatural := by
  intro n
  simp [fromNatural, ofNat, toNatural, toNatural']
  have := preToNatural_some (n, 0) (Nat.zero_le n)
  simp only [sub_zero] at this
  simp [this]

theorem toNatural_fromNatural_left_inverse : Function.LeftInverse fromNatural toNatural := by
  unfold Function.LeftInverse
  intro a
  simp [toNatural, toNatural']

Last updated: May 02 2025 at 03:31 UTC