Skip to content

Commit

Permalink
feat: Rat-dependent norm_num functionality (#1441)
Browse files Browse the repository at this point in the history
As per #1102, we extend `norm_num` to handle `Rat`s.

We leave most of the theorems sorried, but the evaluation and tests work!

Co-authored-by: Mario Carneiro <di.gama@gmail.com>
  • Loading branch information
thorimur and digama0 committed Jan 14, 2023
1 parent 5924d4f commit 3aaa8a5
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 180 deletions.
8 changes: 8 additions & 0 deletions Mathlib/Algebra/Invertible.lean
Expand Up @@ -137,6 +137,11 @@ def Invertible.copy [MulOneClass α] {r : α} (hr : Invertible r) (s : α) (hs :
mul_invOf_self := by rw [hs, mul_invOf_self]
#align invertible.copy Invertible.copy

/-- If `a` is invertible and `a = b`, then `⅟a = ⅟b`. -/
@[congr]
theorem Invertible.cong [Ring α] (a b : α) [Invertible a] (h : a = b) :
⅟a = @Invertible.invOf _ _ _ b (Invertible.copy ‹_› _ h.symm) := by subst h; rfl

/-- An `invertible` element is a unit. -/
@[simps]
def unitOfInvertible [Monoid α] (a : α) [Invertible a] :
Expand Down Expand Up @@ -197,6 +202,9 @@ def invertibleOne [Monoid α] : Invertible (1 : α) :=
#align invertible_one invertibleOne

@[simp]
theorem invOf_one' [Monoid α] {_ : Invertible (1 : α)} : ⅟ (1 : α) = 1 :=
invOf_eq_right_inv (mul_one _)

theorem invOf_one [Monoid α] [Invertible (1 : α)] : ⅟ (1 : α) = 1 :=
invOf_eq_right_inv (mul_one _)
#align inv_of_one invOf_one
Expand Down
9 changes: 5 additions & 4 deletions Mathlib/Algebra/Order/Floor.lean
Expand Up @@ -1440,11 +1440,12 @@ theorem round_eq (x : α) : round x = ⌊x + 1 / 2⌋ := by
· have : ⌊fract x + 1 / 2⌋ = 1 := by
rw [floor_eq_iff]
constructor
. -- Porting note: `add_halves` can be removed, and `norm_num at *` can lose the *, after
-- linarith learns about fractions
have := add_halves (1:α)
. -- norm_num at *
-- linarith
-- Porting note: linarith broke here after the move to ℚ in norm_num.
have := add_le_add_right hx (1/2)
norm_num at *
linarith
assumption
· -- Porting note: `norm_num at *` can lose the *, after linarith learns about fractions
norm_num at *
linarith [fract_lt_one x]
Expand Down
200 changes: 181 additions & 19 deletions Mathlib/Tactic/NormNum/Basic.lean
Expand Up @@ -13,6 +13,8 @@ import Qq.Match
This file adds `norm_num` plugins for `+`, `*` and `^` along with other basic operations.
-/

set_option warningAsError false -- FIXME: prove the sorries

namespace Mathlib
open Lean Meta

Expand Down Expand Up @@ -91,6 +93,13 @@ theorem isInt_add {α} [Ring α] : {a b : α} → {a' b' c : ℤ} →
IsInt a a' → IsInt b b' → Int.add a' b' = c → IsInt (a + b) c
| _, _, _, _, _, ⟨rfl⟩, ⟨rfl⟩, rfl => ⟨(Int.cast_add ..).symm⟩

theorem isRat_add {α} [Ring α] : {a b : α} → {an bn cn : ℤ} → {ad bd cd g : ℕ} →
IsRat a an ad → IsRat b bn bd →
Int.add (Int.mul an bd) (Int.mul bn ad) = Int.mul (Nat.succ g) cn →
Nat.mul ad bd = Nat.mul (Nat.succ g) cd →
IsRat (a + b) cn cd
| _, _, _, _, _, _, _, _, _, ⟨_, rfl⟩, ⟨_, rfl⟩, h₁, h₂ => sorry

instance : MonadLift Option MetaM where
monadLift
| none => failure
Expand All @@ -102,10 +111,10 @@ such that `norm_num` successfully recognises both `a` and `b`. -/
let .app (.app f (a : Q($α))) (b : Q($α)) ← withReducible (whnf e) | failure
let ra ← derive a; let rb ← derive b
match ra, rb with
| .isNat _ .., .isNat _ .. | .isNat _ .., .isNegNat _ ..
| .isNegNat _ .., .isNat _ .. | .isNegNat _ .., .isNegNat _ .. =>
| .isNat _ .., .isNat _ .. | .isNat _ .., .isNegNat _ .. | .isNat _ .., .isRat _ ..
| .isNegNat _ .., .isNat _ .. | .isNegNat _ .., .isNegNat _ .. | .isNegNat _ .., .isRat _ ..
| .isRat _ .., .isNat _ .. | .isRat _ .., .isNegNat _ .. | .isRat _ .., .isRat _ .. =>
guard <|← withNewMCtxDepth <| isDefEq f q(HAdd.hAdd (α := $α))
| _, _ => failure
let rec
/-- Main part of `evalAdd`. -/
core : Option (Result e) := do
Expand All @@ -114,7 +123,20 @@ such that `norm_num` successfully recognises both `a` and `b`. -/
let zc := za + zb
have c := mkRawIntLit zc
let r : Q(Int.add $na $nb = $c) := (q(Eq.refl $c) : Expr)
return (.isInt rα zc q(isInt_add $pa $pb $r) : Result q($a + $b))
return (.isInt rα c zc q(isInt_add $pa $pb $r) : Result q($a + $b))
let ratArm (dα : Q(DivisionRing $α)) : Result _ :=
let ⟨qa, na, da, pa⟩ := ra.toRat'; let ⟨qb, nb, db, pb⟩ := rb.toRat'
let qc := qa + qb
let dd := qa.den * qb.den
let g := dd / qc.den - 1
have nc : Q(ℤ) := mkRawIntLit qc.num
have dc : Q(ℕ) := mkRawNatLit qc.den
have g : Q(ℕ) := mkRawNatLit g
let r1 : Q(Int.add (Int.mul $na $db) (Int.mul $nb $da) = Int.mul (Nat.succ $g) $nc) :=
(q(Eq.refl (Int.mul (Nat.succ $g) $nc)) : Expr)
have t2 : Q(ℕ) := mkRawNatLit dd
let r2 : Q(Nat.mul $da $db = Nat.mul (Nat.succ $g) $dc) := (q(Eq.refl $t2) : Expr)
(.isRat dα qc nc dc q(isRat_add $pa $pb $r1 $r2) : Result q($a + $b))
match ra, rb with
| .isNat _ na pa, .isNat sα nb pb =>
have pa : Q(IsNat $a $na) := pa
Expand All @@ -124,40 +146,95 @@ such that `norm_num` successfully recognises both `a` and `b`. -/
| .isNat _ .., .isNegNat rα ..
| .isNegNat rα .., .isNat _ ..
| .isNegNat _ .., .isNegNat rα .. => intArm rα
| _, _ => failure
| _, .isRat dα .. | .isRat dα .., _ => some (ratArm dα)
core

theorem isInt_neg {α} [Ring α] : {a : α} → {a' b : ℤ} →
IsInt a a' → Int.neg a' = b → IsInt (-a) b
| _, _, _, ⟨rfl⟩, rfl => ⟨(Int.cast_neg ..).symm⟩

theorem isRat_neg {α} [Ring α] : {a : α} → {n n' : ℤ} → {d : ℕ} →
IsRat a n d → Int.neg n = n' → IsRat (-a) n' d
| _, _, _, _, ⟨_, rfl⟩, rfl => sorry

/-- The `norm_num` extension which identifies expressions of the form `-a`,
such that `norm_num` successfully recognises `a`. -/
@[norm_num -_] def evalNeg : NormNumExt where eval {u α} e := do
let .app f (a : Q($α)) ← withReducible (whnf e) | failure
let ra ← derive a
let rα ← inferRing α
guard <|← withNewMCtxDepth <| isDefEq f q(Neg.neg (α := $α))
let ⟨za, na, pa⟩ ← (← derive a).toInt
let zb := -za
have b := mkRawIntLit zb
let r : Q(Int.neg $na = $b) := (q(Eq.refl $b) : Expr)
return (.isInt rα zb (z := b) q(isInt_neg $pa $r) : Result q(-$a))
let rec
/-- Main part of `evalAdd`. -/
core : Option (Result e) := do
let intArm (rα : Q(Ring $α)) := do
let ⟨za, na, pa⟩ ← ra.toInt
let zb := -za
have b := mkRawIntLit zb
let r : Q(Int.neg $na = $b) := (q(Eq.refl $b) : Expr)
return (.isInt rα b zb q(isInt_neg $pa $r) : Result q(-$a))
let ratArm (dα : Q(DivisionRing $α)) : Result _ :=
by clear rα; exact
let ⟨qa, na, da, pa⟩ := ra.toRat'
let qb := -qa
have nb := mkRawIntLit qb.num
let r : Q(Int.neg $na = $nb) := (q(Eq.refl $nb) : Expr)
(.isRat' dα qb nb da q(isRat_neg $pa $r) : Result q(-$a))
match ra with
| .isNat _ .. => intArm rα
| .isNegNat rα .. => intArm rα
| .isRat dα .. => some (ratArm dα)
core

theorem isInt_sub {α} [Ring α] : {a b : α} → {a' b' c : ℤ} →
IsInt a a' → IsInt b b' → Int.sub a' b' = c → IsInt (a - b) c
| _, _, _, _, _, ⟨rfl⟩, ⟨rfl⟩, rfl => ⟨(Int.cast_sub ..).symm⟩

theorem isRat_sub {α} [Ring α] : {a b : α} → {an bn cn : ℤ} → {ad bd cd g : ℕ} →
IsRat a an ad → IsRat b bn bd →
Int.sub (Int.mul an bd) (Int.mul bn ad) = Int.mul (Nat.succ g) cn →
Nat.mul ad bd = Nat.mul (Nat.succ g) cd →
IsRat (a - b) cn cd
| _, _, _, _, _, _, _, _, _, ⟨_, rfl⟩, ⟨_, rfl⟩, h₁, h₂ => sorry

/-- The `norm_num` extension which identifies expressions of the form `a - b`,
such that `norm_num` successfully recognises both `a` and `b`. -/
@[norm_num _ - _, Sub.sub _ _] def evalSub : NormNumExt where eval {u α} e := do
let .app (.app f (a : Q($α))) (b : Q($α)) ← withReducible (whnf e) | failure
let ra ← derive a; let rb ← derive b
let rα ← inferRing α
guard <|← withNewMCtxDepth <| isDefEq f q(HSub.hSub (α := $α))
let ⟨za, na, pa⟩ ← (← derive a).toInt; let ⟨zb, nb, pb⟩ ← (← derive b).toInt
let zc := za - zb
have c := mkRawIntLit zc
let r : Q(Int.sub $na $nb = $c) := (q(Eq.refl $c) : Expr)
return (.isInt rα zc (z := c) q(isInt_sub $pa $pb $r) : Result q($a - $b))
let rec
/-- Main part of `evalAdd`. -/
core : Option (Result e) := do
let intArm (rα : Q(Ring $α)) := do
let ⟨za, na, pa⟩ ← ra.toInt; let ⟨zb, nb, pb⟩ ← rb.toInt
let zc := za - zb
have c := mkRawIntLit zc
let r : Q(Int.sub $na $nb = $c) := (q(Eq.refl $c) : Expr)
return (.isInt rα c zc q(isInt_sub $pa $pb $r) : Result q($a - $b))
let ratArm (dα : Q(DivisionRing $α)) : (Result _) :=
by clear rα; exact
let ⟨qa, na, da, pa⟩ := ra.toRat'; let ⟨qb, nb, db, pb⟩ := rb.toRat'
let qc := qa - qb
let dd := qa.den * qb.den
let gsucc := dd / qc.den
have nc : Q(ℤ) := mkRawIntLit qc.num
have dc : Q(ℕ) := mkRawNatLit qc.den
have g : Q(ℕ) := mkRawNatLit (gsucc - 1)
have t1 : Q(ℤ) := mkRawIntLit (gsucc * qc.num)
have t2 : Q(ℕ) := mkRawNatLit dd
let r1 : Q(Int.sub (Int.mul $na $db) (Int.mul $nb $da) = Int.mul (Nat.succ $g) $nc) :=
(q(Eq.refl $t1) : Expr)
let r2 : Q(Nat.mul $da $db = Nat.mul (Nat.succ $g) $dc) := (q(Eq.refl $t2) : Expr)
(.isRat dα qc nc dc q(isRat_sub $pa $pb $r1 $r2) : Result q($a - $b))
match ra, rb with
| .isNat _ .., .isNat _ ..
| .isNat _ .., .isNegNat rα ..
| .isNegNat rα .., .isNat _ ..
| .isNegNat _ .., .isNegNat rα .. => intArm rα
| _, .isRat dα .. | .isRat dα .., _ => some (ratArm dα)
core

theorem isNat_mul {α} [Semiring α] : {a b : α} → {a' b' c : ℕ} →
IsNat a a' → IsNat b b' → Nat.mul a' b' = c → IsNat (a * b) c
Expand All @@ -167,6 +244,13 @@ theorem isInt_mul {α} [Ring α] : {a b : α} → {a' b' c : ℤ} →
IsInt a a' → IsInt b b' → Int.mul a' b' = c → IsInt (a * b) c
| _, _, _, _, _, ⟨rfl⟩, ⟨rfl⟩, rfl => ⟨(Int.cast_mul ..).symm⟩

theorem isRat_mul {α} [Ring α] : {a b : α} → {an bn cn : ℤ} → {ad bd cd g : ℕ} →
IsRat a an ad → IsRat b bn bd →
Int.mul an bn = Int.mul (Nat.succ g) cn →
Nat.mul ad bd = Nat.mul (Nat.succ g) cd →
IsRat (a * b) cn cd
| _, _, _, _, _, _, _, _, _, ⟨_, rfl⟩, ⟨_, rfl⟩, h₁, h₂ => sorry

/-- The `norm_num` extension which identifies expressions of the form `a * b`,
such that `norm_num` successfully recognises both `a` and `b`. -/
@[norm_num _ * _, Mul.mul _ _] def evalMul : NormNumExt where eval {u α} e := do
Expand All @@ -182,7 +266,20 @@ such that `norm_num` successfully recognises both `a` and `b`. -/
let zc := za * zb
have c := mkRawIntLit zc
let r : Q(Int.mul $na $nb = $c) := (q(Eq.refl $c) : Expr)
return (.isInt rα zc (z := c) (q(isInt_mul $pa $pb $r) : Expr) : Result q($a * $b))
return (.isInt rα c zc (q(isInt_mul $pa $pb $r) : Expr) : Result q($a * $b))
let ratArm (dα : Q(DivisionRing $α)) : Result _ :=
let ⟨qa, na, da, pa⟩ := ra.toRat'; let ⟨qb, nb, db, pb⟩ := rb.toRat'
let qc := qa * qb
let dd := qa.den * qb.den
let g := dd / qc.den - 1
have nc : Q(ℤ) := mkRawIntLit qc.num
have dc : Q(ℕ) := mkRawNatLit qc.den
have g : Q(ℕ) := mkRawNatLit g
let r1 : Q(Int.mul $na $nb = Int.mul (Nat.succ $g) $nc) :=
(q(Eq.refl (Int.mul $na $nb)) : Expr)
have t2 : Q(ℕ) := mkRawNatLit dd
let r2 : Q(Nat.mul $da $db = Nat.mul (Nat.succ $g) $dc) := (q(Eq.refl $t2) : Expr)
(.isRat dα qc nc dc q(isRat_mul $pa $pb $r1 $r2) : Result q($a * $b))
match ra, rb with
| .isNat _ na pa, .isNat mα nb pb =>
let pa : Q(@IsNat _ AddCommMonoidWithOne.toAddMonoidWithOne $a $na) := pa
Expand All @@ -193,7 +290,7 @@ such that `norm_num` successfully recognises both `a` and `b`. -/
| .isNat _ .., .isNegNat rα ..
| .isNegNat rα .., .isNat _ ..
| .isNegNat _ .., .isNegNat rα .. => intArm rα
| _, _ => failure
| _, .isRat dα .. | .isRat dα .., _ => some (ratArm dα)
core

theorem isNat_pow {α} [Semiring α] : {a : α} → {b a' b' c : ℕ} →
Expand All @@ -204,6 +301,12 @@ theorem isInt_pow {α} [Ring α] : {a : α} → {b : ℕ} → {a' : ℤ} → {b'
IsInt a a' → IsNat b b' → Int.pow a' b' = c → IsInt (a ^ b) c
| _, _, _, _, _, ⟨rfl⟩, ⟨rfl⟩, rfl => ⟨by simp⟩

theorem isRat_pow {α} [Ring α] : {a : α} → {an cn : ℤ} → {ad b b' cd : ℕ} →
IsRat a an ad → IsNat b b' →
Int.pow an b' = cn → Nat.pow ad b' = cd →
IsRat (a ^ b) cn cd
| _, _, _, _, _, _, _, ⟨_, rfl⟩, ⟨rfl⟩, rfl, rfl => sorry

/-- The `norm_num` extension which identifies expressions of the form `a ^ b`,
such that `norm_num` successfully recognises both `a` and `b`, with `b : ℕ`. -/
@[norm_num (_ : α) ^ (_ : ℕ), Pow.pow _ (_ : ℕ)]
Expand All @@ -228,6 +331,65 @@ def evalPow : NormNumExt where eval {u α} e := do
let zc := za ^ nb.natLit!
let c := mkRawIntLit zc
let r : Q(Int.pow $na $nb = $c) := (q(Eq.refl $c) : Expr)
return (.isInt rα zc (z := c) (q(isInt_pow $pa $pb $r) : Expr) : Result q($a ^ $b))
| _ => failure
return (.isInt rα c zc (q(isInt_pow $pa $pb $r) : Expr) : Result q($a ^ $b))
| .isRat dα qa na da pa =>
let qc := qa ^ nb.natLit!
have nc : Q(ℤ) := mkRawIntLit qc.num
have dc : Q(ℕ) := mkRawNatLit qc.den
have r1 : Q(Int.pow $na $nb = $nc) := (q(Eq.refl $nc) : Expr)
have r2 : Q(Nat.pow $da $nb = $dc) := (q(Eq.refl $dc) : Expr)
return (.isRat dα qc nc dc (q(isRat_pow $pa $pb $r1 $r2) : Expr) : Result q($a ^ $b))
core

theorem isRat_inv_pos {α} [DivisionRing α] : {a : α} → {n d : ℕ} →
Nat.ble (nat_lit 1) n = true → IsRat a (.ofNat n) d → IsRat a⁻¹ (.ofNat d) n
| _, _, _, _, ⟨_, rfl⟩ => sorry

theorem isRat_inv_zero {α} [DivisionRing α] : {a : α} →
IsNat a (nat_lit 0) → IsNat a⁻¹ (nat_lit 0)
| _, ⟨rfl⟩ => ⟨by simp⟩

theorem isRat_inv_neg {α} [DivisionRing α] : {a : α} → {n d : ℕ} →
Nat.ble 1 n = true → IsRat a (.negOfNat n) d → IsRat a⁻¹ (.negOfNat d) n
| _, _, _, _, ⟨_, rfl⟩ => sorry

/-- The `norm_num` extension which identifies expressions of the form `a⁻¹`,
such that `norm_num` successfully recognises `a`. -/
@[norm_num _⁻¹] def evalInv : NormNumExt where eval {u α} e := do
let .app f (a : Q($α)) ← withReducible (whnf e) | failure
let ra ← derive a
let dα ← inferDivisionRing α
guard <|← withNewMCtxDepth <| isDefEq f q(Inv.inv (α := $α))
let ⟨qa, na, da, pa⟩ := ra.toRat'
let qb := qa⁻¹
if qa > 0 then
have lit : Q(ℕ) := na.appArg!
let pa : Q(IsRat «$a» (Int.ofNat $lit) $da) := pa
let r : Q(Nat.ble (nat_lit 1) $lit = true) := (q(Eq.refl true) : Expr)
return (.isRat' dα qb q(.ofNat $da) lit
(q(isRat_inv_pos (α := $α) $r $pa) : Expr) : Result q($a⁻¹))
else if qa < 0 then
have lit : Q(ℕ) := na.appArg!
let pa : Q(IsRat «$a» (Int.negOfNat $lit) $da) := pa
let r : Q(Nat.ble (nat_lit 1) $lit = true) := (q(Eq.refl true) : Expr)
return (.isRat' dα qb q(.negOfNat $da) lit
(q(isRat_inv_neg (α := $α) $r $pa) : Expr) : Result q($a⁻¹))
else
let .isNat inst _z (pa : Q(@IsNat _ AddGroupWithOne.toAddMonoidWithOne $a (nat_lit 0))) := ra
| failure
return (.isNat inst _z (q(isRat_inv_zero $pa) : Expr) : Result q($a⁻¹))

theorem isRat_div {α} [DivisionRing α] : {a b : α} → {cn : ℤ} → {cd : ℕ} → IsRat (a * b⁻¹) cn cd →
IsRat (a / b) cn cd
| _, _, _, _, h => by simp [div_eq_mul_inv]; exact h

/-- The `norm_num` extension which identifies expressions of the form `a / b`,
such that `norm_num` successfully recognises both `a` and `b`. -/
@[norm_num _ / _, Div.div _ _] def evalDiv : NormNumExt where eval {u α} e := do
let .app (.app f (a : Q($α))) (b : Q($α)) ← withReducible (whnf e) | failure
let dα ← inferDivisionRing α
guard <|← withNewMCtxDepth <| isDefEq f q(HDiv.hDiv (α := $α))
let rab ← derive (q($a * $b⁻¹) : Q($α))
let ⟨qa, na, da, pa⟩ := rab.toRat'
let pa : Q(IsRat ($a * $b⁻¹) $na $da) := pa
return (.isRat' dα qa na da q(isRat_div $pa) : Result q($a / $b))

0 comments on commit 3aaa8a5

Please sign in to comment.